Repository navigation
Conversation
…used Django mirrors the shared sandbox SES tenant state from the tenant-state webhook and the daily reconciliation. The worker skips every sandbox send while that tenant is paused, and checks again after the daily allowance claim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
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.
Note
Replaced by #607 (Django mirror) and #608 (worker pause gate). This PR mixed lanes that upstream's merge rules keep apart, so it was split in two. Same code, carved by file.
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, slice 2 of 3 there). Stacked on #604.
Replaces #452 and #453.
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 · one-way door (merge-risk rubric)
workflows_sandboxsendertenantstate(migration0039). It is a plainCREATE TABLEwith no foreign keys, so the migration takes no lock on an existing table.SES_SANDBOX_TENANT_NAME. With the setting empty, they do nothing new.get_tenant_sending_state. Team tenant tests pin the same results.sandboxintegration read the new state. A sandbox send fails closed when the read fails.Production lines: 292 changed (
git diff --numstatagainst #604; tests, docs and the migration excluded).Changes
DISABLED, or when the worker cannot read its state. The run log and aworkflows sandbox email blockedevent with reasonpausedrecord the skip.SandboxSenderTenantState:reload-sandbox-sender-stateto the workers, which drop their cached state.SES_SANDBOX_TENANT_NAMEis a new Django setting. Set the same value on Django web, Celery, and the worker.How did you test this code?
tscpasses. eslint, oxlint, prettier and ruff pass on the changed files.makemigrations --checkreports no changes, andsqlmigrate workflows 0039shows oneCREATE TABLE.semgrep --config .semgrep/rules/security/reports nothing on the lines this PR adds.sandbox-sender-state.service.test.ts, thepause gateblock ofemail.service.test.ts, andtest_ses_tenant_state.pycannot load, because the state service and model do not exist.email.service.test.ts,sandbox-email-sender.test.ts,rate-limiter.service.test.ts,sandbox-sender-state.service.test.ts, and the Django suitestest_ses_tenant_state.py,test_ses_tenant_events.py,test_email_sending_tier.pyandtest_email_reputation.py.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.sync_sandbox_tenant_state, the tenant-state webhook, andreconcile_ses_tenant_states, with the SES client mocked.fleet proof gate):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. The Django tests cover the webhook and sweep paths that write the mirror, including a failed sandbox sync that must not stop the team sweep.Release status
Docs update
docs/internal/workflows-email-sending-tiers.mddocuments the pause gate, the mirror, andSES_SANDBOX_TENANT_NAME.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (implementation). Design pass and review by Codex, GPT-6.1 Sol.
Ported from the reviewed source PRs above onto the caps PR as a wayfinder ticket lane. Skills:
tdd,/writing-tests,/django-migrations,pr-description,merge-risk,qa-swarmdesign pass.No duplicate: this replaces the source fork PRs named above, which stay open until the stack exists.
Public artifact: all test data is invented.
Decisions
SES_SANDBOX_TENANT_NAMElives0039, after master's0038Involvement: no grilling rounds; design pass (1 lane, 1 minor finding fixed: the reload uses
transaction.on_commit(..., robust=True)); review (GPT-6.1 Sol, 1 round, no findings); proof captured as test output.🤖 Generated with Claude Code