Repository navigation
fix(messaging): refuse a transactional email to a recipient that can't receive email - #123878
trunk-io[bot] merged 2 commits into
Conversation
…t receive email The transactional email API answered 202 for a send the email worker later skipped because the recipient address is invalid or its domain has no mail servers. The API now runs the same recipient check before it answers and refuses with 422 recipient_unreachable. EmailValidationService.getRecipientSkipReason splits the per-address check out of getSkipReason, so the worker and the API share it. The API runs it last, because a cache miss costs a DNS lookup. DNS failures fail open. Django maps the new pipeline code to RecipientUnreachable. Co-Authored-By: Claude Opus 5.5 <[email protected]> Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
|
😎 Merged successfully - details. |
🤖 CI report
|
|
|
@PostHog review --deep |
|
@PostHog review |
|
Risk: No findings Since the last review this PR only merged master and updated a test to match master's new behavior (quota-limited transactional sends queue instead of refusing), plus added two echo fields to the preferences-token validation response. The two new response fields are claims from a server-signed, fail-closed-verified JWT, so they disclose nothing the token holder could not already decode from the token itself, and the recipient-unreachable refusal chain is intact after the merge. Sentinel reviewed |
There was a problem hiding this comment.
Approved.
The disclosed API and send-path changes have independent assurance from a current-head review and owning-team authorship. No unresolved substantive review concerns or other policy refusal grounds were found.
- Author wrote 50% of the modified lines and has 39 merged PRs in these paths (familiarity MODERATE).
- PostHog Review (Standard) reviewed the current head: no findings.
- posthog[bot] reviewed the current head.
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 41L, 7F substantive, 52L/9F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1c-medium (52L, 9F, two-areas, fix) |
| stamphog 2.6.1 | .stamphog/policy.yml @ 5289444 · reviewed head 5289444 |
…able-recipient Generated-By: PostHog Desktop Task-Id: e72a2c10-f145-4f7c-8754-a4fcc06a18be
|
/trunk merge |
|
An error occurred while submitting your PR to the queue: |
Problem
202 queued, but the email never goes out.Refs #120921
Changes
422with the new error coderecipient_unreachableand a message that asks the caller to check the address for a typo.refuseTransactionalEmailin the Node CDP API runs the same recipient check as the worker, through the newEmailValidationService.getRecipientSkipReason(teamId, email).getRecipientSkipReasonis split out ofgetSkipReason, so the worker and the API share one check.RecipientUnreachablecontract, aPIPELINE_REFUSALSentry, and a 422 inTransactionalEmailRejected.STATUS_BY_CODE.createCdpCoreServicesexposesemailValidationService, and the 422 OpenAPI description mentions the new case.How did you test this code?
Test rationale: The new case in
cdp-api.serial.test.ts("refuses a send the email worker would skip because the recipient's domain has no mail servers") catches the API answering 202 for a send the worker would skip. It extends the existing parameterized refusal test. With the newrefuseTransactionalEmaillines removed, it fails (202 instead of 422). The new row intest_reports_a_send_the_pipeline_refusescatches Django returning a server error for the new pipeline code. AbeforeEachstub makesgetRecipientSkipReasonreturn null for the other cases, because the fixtures send to example.com, which publishes a null MX.Run locally:
cdp-api.serial.test.tswith-t "transactional email", andemail-validation.service.test.ts.products/messaging/backend/tests/api/test_transactional_email.py.Not run: a real send against a live DNS resolver, or manual testing in a running stack.
Release status
Docs update
None in this repo. The transactional email docs page (PostHog/posthog.com#21564) should list the new
recipient_unreachableerror code.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: PostHog Desktop (Claude Code), Claude Opus 5.5
/writing-pr-descriptions.gh pr list --state open --search "recipient unreachable transactional"found no open PR.🤖 Generated with Claude Code
Created with PostHog Desktop