From 1e1e5176a3cecafbafd33bedac1f1f0f9aeb66d2 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:24:32 -0400 Subject: [PATCH] Fix payroll summary email (SES config-set permission) + isolate failures (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix payroll email: grant SES config-set permission + isolate failures The weekly pay-summary email to payroll has been failing with SES AccessDenied since 2026-06-08. The sending identity (seahaven.com) gained a default configuration set (seahaven-email-events), and SES authorizes SendEmail against the config-set ARN as well as the identity — but the WeeklyPostFunction role only granted ses:SendEmail on identity/*. - template.yaml: add the configuration-set ARN (scoped to the known set name) to the SES policy so sends are authorized again. - weekly-post/app.py: wrap _send_pay_email in try/except so a delivery failure can never abort the handler before the Slack schedule post. Previously the SES error also blocked the two-week schedule post. - Add a regression test covering the isolation. Cross-family GPT-4.1 IAM review: APPROVE. * Bump to v1.10.1 in CHANGELOG and sync App Home copy --- CHANGELOG.md | 9 +++++++++ src/slack-bot/CHANGELOG.md | 9 +++++++++ src/weekly-post/app.py | 12 ++++++++++-- template.yaml | 5 +++++ tests/weekly_post/test_handler.py | 21 +++++++++++++++++++++ 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9114b..6418eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ fine and still supported. --- +## v1.10.1 — June 15, 2026 + +**Payroll summary emails are sending again.** The automated weekly pay summary +to payroll had stopped going out (an email-permissions change on our side blocked +it from June 8 onward). Fixed the permission so the emails send, and made the +Monday job sturdier: if the payroll email ever fails again, it no longer stops +the on-call schedule from being posted in Slack. The two missed summaries were +re-sent by hand. + ## v1.10.0 — June 11, 2026 **The bot now tells you what's new.** Two things: diff --git a/src/slack-bot/CHANGELOG.md b/src/slack-bot/CHANGELOG.md index 0f9114b..6418eb7 100644 --- a/src/slack-bot/CHANGELOG.md +++ b/src/slack-bot/CHANGELOG.md @@ -10,6 +10,15 @@ fine and still supported. --- +## v1.10.1 — June 15, 2026 + +**Payroll summary emails are sending again.** The automated weekly pay summary +to payroll had stopped going out (an email-permissions change on our side blocked +it from June 8 onward). Fixed the permission so the emails send, and made the +Monday job sturdier: if the payroll email ever fails again, it no longer stops +the on-call schedule from being posted in Slack. The two missed summaries were +re-sent by hand. + ## v1.10.0 — June 11, 2026 **The bot now tells you what's new.** Two things: diff --git a/src/weekly-post/app.py b/src/weekly-post/app.py index 1fded02..e904409 100644 --- a/src/weekly-post/app.py +++ b/src/weekly-post/app.py @@ -183,8 +183,16 @@ def handler(event, context): else: logger.warning("PAY_REPORT_USER not set — skipping Slack pay summary") - # Email pay summary to payroll - _send_pay_email(week_label, pay_record) + # Email pay summary to payroll. Isolated so a delivery failure (e.g. + # an SES permission/identity issue) can never abort the rest of the + # handler — the Slack schedule post below must still go out. + try: + _send_pay_email(week_label, pay_record) + except Exception: + logger.exception( + "Failed to send pay summary email to payroll for week of %s", + week_key, + ) # --- Delete previous week's schedule post --- old_post = schedule.get_schedule_post(channel_id) diff --git a/template.yaml b/template.yaml index c2bb6f6..6a1dcf3 100644 --- a/template.yaml +++ b/template.yaml @@ -138,6 +138,11 @@ Resources: - ses:SendEmail Resource: - !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/*" + # The sending identity has a default configuration set + # (seahaven-email-events); SES authorizes SendEmail against the + # config-set resource too, so it must be granted alongside the + # identity or the send is denied. Scoped to the known set name. + - !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/seahaven-email-events" Events: # EST: 7am ET = 12:00 UTC (Nov-Mar) WeeklyPostEST: diff --git a/tests/weekly_post/test_handler.py b/tests/weekly_post/test_handler.py index cb80609..0100185 100644 --- a/tests/weekly_post/test_handler.py +++ b/tests/weekly_post/test_handler.py @@ -60,6 +60,27 @@ def test_calculates_and_dms_pay(weeklypost_app, schedule, seed, slack, env): assert dm_calls +@freeze_time(MON_0800) +def test_pay_email_failure_does_not_block_schedule_post( + weeklypost_app, schedule, seed, slack, env, monkeypatch +): + # Previous week has an assigned shift, so the pay/email path runs. + seed.config(shift_rate="50") + seed.weekly("Monday", "114", "Alice") + # SES delivery blows up (e.g. a permission/identity issue). + monkeypatch.setattr( + weeklypost_app, + "_send_pay_email", + MagicMock(side_effect=Exception("SES AccessDenied")), + ) + + result = weeklypost_app.handler({"force": True}, None) + + # The email failure is swallowed; the schedule post still goes out. + assert result["posted"] is True + assert schedule.get_schedule_post("C_TEST")["message_ts"] == "999.000" + + @freeze_time(MON_0800) def test_deletes_previous_schedule_post(weeklypost_app, schedule, seed, slack, env): seed.schedule_post("C_TEST", "111.111")