Repository navigation
Conversation
The email worker reads SandboxSenderOrganizationBlock in the team organization lookup. While a block row exists, the organization's sandbox sends skip with the reason organization_blocked and a run-log message that points to support. The check runs inside the recipient check, so a blocked organization spends no daily caps and the final check before sending also blocks. A block reaches the workers within one minute. Own-sender sends do not read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
workflows-sandbox/org-block-worker
branch
from
October 26, 2026 08:14
86359b8 to
4e16aa2
Compare
A send through a `sandbox` email integration now skips unless the worker switch and its three SES route settings are set, and unless every To, CC and BCC address belongs to an active, verified member of the team's active organization. The check runs again right before delivery. Each skip logs a reason and emits a deduplicated blocked event. The sandbox route does not exist yet, so a send that passes every check still skips as unavailable instead of reporting success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sandbox send reaches only the team's own members, so it must not spend or wait on the team's tier budget that its real sends share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sandbox send that passes the member gate now goes out through the fixed SES tenant, configuration set and From address, with a footer that names the sender and the organization. Tracking, unsubscribe headers, the team's tier budget and custom From/Reply-To settings do not apply. The check on the final recipient list runs right before delivery, and each delivered send emits a sandbox sent event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ier history Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sandbox sends claim a daily allowance per project and per recipient address across the organization right before delivery. A reached allowance skips the send. SES throttles give the claim back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CDP API's SES Valkey pool no longer stops the process on repeated errors, and it bounds connection and command waits, so an outage skips sandbox sends instead. A claim now carries its own return deadline, so an old claim cannot refund into a refilled bucket. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Django records the pause state of the shared sandbox SES tenant in SandboxSenderTenantState, from the tenant-state webhook and the daily reconciliation, and announces each committed change to the workers. Nothing reads the table yet; the worker gate follows in its own PR. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
The tier sweep subtracts email_sandbox_sent from email_sent per day, and the sending allowance subtracts it per workflow, both floored at zero. No code records email_sandbox_sent yet, so the tier does not change until the worker metric lands in its own PR. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A successful sandbox send records email_sandbox_sent next to email_sent, so the sending tier can leave it out while the workflow metrics still show it. The CDP API's SES webhook captures a workflows sandbox email delivered event for deliveries on the sandbox configuration set. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…subtraction The allowance case now gives one workflow more sandbox sends than sends, so a team-wide subtraction would erase the other workflow's own sends. Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws-sandbox-delivery/sandbox-metric
Refs Silthus/personal#199 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dbox stack Merge the new base, workflows-sandbox-delivery/sandbox-metric, into this branch, and keep only this PR's change on top of it. The worker reads the block with the organization members. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
changed the base branch from
workflows-sandbox/org-block
to
workflows-sandbox-delivery/sandbox-metric
October 27, 2026 12:31
…shot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Note 🤖 Claude Opus 5.5 responding on behalf of Michael Decisions for this PR:
|
Silthus
force-pushed
the
workflows-sandbox-delivery/sandbox-metric
branch
from
October 28, 2026 10:00
69afe3e to
7e95863
Compare
…ock layer Merge workflows-sandbox/org-block (#586) without a force-push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
changed the base branch from
workflows-sandbox-delivery/sandbox-metric
to
workflows-sandbox/org-block
October 28, 2026 11:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The block layer documents the admin and the endpoint, so this layer only adds the worker side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
A staff block from #586 stops new sandbox senders, but an organization's existing workflows keep sending sandbox email.
Stack: V3, protect shared reputation: #607 → #608 → #609 → #610 → #586 → this PR. #586 ships the block table this worker reads.
This fork PR moves to an upstream PostHog/posthog PR after review.
Impact
Pays into Grow Workflows through activation rate (measured here). In scope: Messaging, email sending. Expected move: small, up, as a rollout guard for the sandbox sender. Run cost: n/a.
Note
🟢 Merge risk: low · two-way door (merge-risk rubric)
WORKFLOWS_SANDBOX_SENDER_ENABLEDand theworkflows-sandbox-senderflag.Changes
organization_blocked, notrecipient_not_member.reload-sandbox-sender-staterefreshes only the shared tenant pause, so it cannot make a block take effect at once. The one-minute snapshot covers the block.How did you test this code?
Test rationale:
email.service.test.ts, "a running worker stops sandbox sends within a minute of a block and resumes within a minute of its removal", for stored and test runs. One warm service and a controlled clock send, block, skip, unblock and send again. It catches a block that is ignored, folded into another reason, cached past the minute, or never lifted. Red before the change: the blocked send went out. Raising the snapshot age to an hour makes it fail.Live run at
53833496c73. The realCdpApiapp ran stored workflow test runs against a lane test database and a local SES fake. One block row, written as the admin writes it. On this base a skipped email step moves the flow on to its exit, so the run reportssuccess. The skip shows as zero SES requests, the log line, and the event.organization_blockedCase e sends no second event, because blocked events go out once per team, reason and mode per hour.
Cross-version. Django writes the block row and the email worker reads it.
Both outcomes are accepted. A block takes effect once the workers run this code. The table ships in #586, beneath this PR.
Gaps.
Release status
Docs update
docs/internal/workflows-email-sending-tiers.mdsays a blocked organization's sandbox sends skip, with the reasonorganization_blockedand a one-minute reach. #586 documents the admin and the endpoint.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (
claude-opus-5-5)impact,tdd,writing-user-facing-copy,writing-pr-descriptions,pr-description.fleet proofholds the live run and the cross-version run at53833496c73, plus the prod-shape gap.fleet proof review. Round 1, on the old stack, found that the Node test only exercised cold caches. The warm-worker test above carries that fix. Round 2 read the PR's diff against feat(workflows): record sandbox deliveries as their own metric #610. It found that the block check and the member check read the snapshot separately, so a refresh between them could let a blocked send through.c30c802a210decides both from one snapshot. The final read onc30c802a210found nothing, and so did the final reads after the V3 restack, onfcc109f116eand53833496c73.hogli ci:preflight --strict.tsc --noEmitonfcc109f116ereports errors only from a native module this box does not build, in files this PR does not touch.fleet proof gate: passes on53833496c73, with the CI gap accepted.Involvement: no grilling rounds, two cross-model review rounds and four final reads, proof captured.
🤖 Generated with Claude Code