Repository navigation
Conversation
This was referenced Oct 27, 2026
…used The email worker reads the sandbox SES tenant state that Django mirrors, skips every sandbox send while the tenant is paused or the state cannot be read, and reloads the state on the reload-sandbox-sender-state message. After the daily allowance claim it checks the pause again and gives the claim back when the pause blocks delivery. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
workflows-sandbox-delivery/tenant-pause-mirror
branch
from
October 28, 2026 10:00
e8da316 to
c8ee93b
Compare
Silthus
force-pushed
the
workflows-sandbox-delivery/tenant-pause-gate
branch
from
October 28, 2026 10:00
0e5e737 to
35ce760
Compare
This was referenced Oct 28, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
All sandbox sends go out through one shared PostHog SES tenant.
When AWS pauses that tenant, for example over its reputation, the worker keeps trying to send through it.
This PR makes every sandbox send skip while the shared tenant is paused, or while its state cannot be read.
Part of Silthus/personal#199 (sandbox delivery). Stacked on #607, which adds the table this PR reads.
Replaces the worker half of #605. Originally #452 and #453.
Stack
workflows-sandbox-delivery/tenant-pause-mirror(feat(workflows): mirror the sandbox ses tenant state in django #607). Lands after feat(workflows): mirror the sandbox ses tenant state in django #607 and every PR below it.35ce76039dd: Node typecheck and lint clean, touched Node suites 264 passed. Review: GPT-6.1 Sol through a delegated review of the restack (each PR's diff, its conflict resolutions, the slice seams and the save gate), no findings.35ce76039dd, 2026-10-28: live run below. Proof gate: fails only on the fork-onlycigap (exit 3); every other check passes.Impact
Grow Workflows, activation rate: the pause gate is a condition for enabling the sandbox sender in production.
No production team sees this yet:
WORKFLOWS_SANDBOX_SENDER_ENABLEDstays off.Merge Danger
Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
sandboxintegration read the new state. Own-sender sends are unchanged.Production lines: 99 changed (
git diff --numstatagainst #607, tests and docs excluded).Changes
DISABLED, or when the worker cannot read its state. The run log and aworkflows sandbox email blockedevent with reasonpausedrecord the skip.reload-sandbox-sender-state.How did you test this code?
Live run on the slice tip
Live run of the V3 tip
7e95863f7a0on a PostHog preview. Stubs: a local SES endpoint that records each call, andexample.comseeded as a valid MX domain; a local SNS signing certificate, so a signed delivery webhook verifies; the trigger event produced straight onto the CDP events topic; personhog pointed at the persons database.SES tenant
DISABLED, then the Django sync: the mirror row readsDISABLED, and the next sandbox send is skipped with "the sandbox sender is paused right now".ENABLEDand a sync: the next send goes out. The worker picked up each change without a restart.A live workflow send on the sandbox sender recorded
email_sentandemail_sandbox_sentonce each, the pair the sending tier subtracts.A signed SES
Deliverynotification for that message on the sandbox configuration set returned 200 and capturedworkflows sandbox email delivered. The same envelope with a forged signature returned 403.Lane gate on HEAD:
tscpasses, and the pre-pushhogli ci:preflight --strictpasses. The lane classifier puts this PR on the Node side only, and it has no migration.Red: on feat(workflows): mirror the sandbox ses tenant state in django #607's code, 6
pause gatecases ofemail.service.test.tsfail.Green on HEAD:
email.service.test.ts,sandbox-email-sender.test.ts,rate-limiter.service.test.tsandsandbox-sender-state.service.test.ts, 255 passed.Test seams:
EmailService.executeSendEmailwith a sandbox integration and a stubbed sender state: skip, run log, event, and the returned claim.SandboxSenderStateServiceagainst the test Postgres and the reload message.Proofs owed (
fleet proof gate):69afe3e4b1f), recorded on every delivery head. One real email worker process (cdp-cyclotron-worker-email) on scratch Postgres, with real Redis, Kafka and ClickHouse: a member received the email, a non-member was skipped, a paused tenant was skipped, the daily team cap held, and each delivered send recordedemail_sandbox_sent, which the tier's own ClickHouse read then left out. Stubbed: the SES endpoint (a local HTTP stub behind the real SESv2 client), the MX verdict for the reservedexample.comdomain, and the tenant-state rows the Django sync would write.master, so it reads as mixed until the Node layers below land. Land the stack bottom-first, not with onetrunk mergeon the top layer.Silthus/posthog.Test rationale: the
pause gateblock drives real sends, because the regressions that matter are a send that goes out through a paused tenant, or a claim kept after the pause blocked delivery.Release status
Docs update
docs/internal/workflows-email-sending-tiers.mddocuments the pause gate.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (implementation). Review by Codex, GPT-6.1 Sol.
Split out of #605 and #606 so each PR passes upstream's merge rules: a migration does not share a PR with
nodejs/, and Python does not share a PR with Node. The code is carved by file from those reviewed heads without changes; the stack's tip has the same tree as #606.Skills:
tdd,/writing-tests,pr-description,merge-risk.Public artifact: all test data is invented.
Decisions
master, so each layer passes once the layers below it land🤖 Generated with Claude Code