Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A deactivated organization now has no verified members, so the sandbox sender blocks every send within the one-minute member snapshot window. Only an explicit false deactivates, matching the web middleware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds SandboxSenderOrganizationBlock, a staff-managed row in the Django admin with the organization, a reason, who added it and when. While a row exists, the ensure endpoint creates no new sandbox sender for the organization's projects. Deleting the row lifts the block. The migration creates the new table only. The email worker starts reading the block in the next layer, so the migration lands before any worker queries the table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82d5d74 to
8ee50d7
Compare
…in forms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…liver Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…working Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…status Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndbox-product/channels-row Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…banner Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sandbox-product/save-validation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nges type A stored email step that turns into a generic function step no longer keeps its sandbox sender as an unchanged value, so the step-type rule rejects it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reopens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sandbox-product/save-validation
The sandbox sender has no delivery route in the worker yet. A send through it now skips with a reason in the logs instead of passing the provider switch as a success. This lands before any API can create a sandbox sender row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…andbox-product/sender-row
…ndbox-product/channels-row
…sandbox-product/save-validation
Workflow email steps list the managed sandbox sender last, tagged Sandbox, with a members-only note once it is selected. Picking it replaces any rotation and clears the custom sender and Reply-To; adding another sender drops it again. Broadcasts, destinations and transactional templates keep leaving it out. Ported from #450 in place in EmailTemplater.tsx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picking the sandbox sender in a workflow email step now asks first when it would replace several senders or a custom sender or Reply-To. Before you send to an audience, a project with only the sandbox sender learns that it reaches only verified members of the organization. Ported from #450. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A generic function step can use the email template too, and saving the sandbox sender there fails. The step now passes its action type, and only a function_email step outside broadcasts offers the sandbox sender. A rendered test checks that both pickers send a pick through chooseSenders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…warnings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lows-sandbox-product/sender-confirmations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lows-sandbox-product/sender-confirmations
A workflow whose email steps use the sandbox sender shows a banner once the project has a verified own sender. The banner switches every sandbox email step to that sender as an unsaved change, behind the workflows-sandbox-sender flag. Refs Silthus/personal#202 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also document the switch banner next to the sandbox sender setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box stack Merge the new base, workflows-sandbox-product/switch-banner, into this branch, and keep only this PR's change on top of it. The migration is now 0039, after the base's 0038. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🤖 CI reportℹ️ MCP agent API — agent-facing tool changesWhat this PR changes for agents, from the tool schema snapshots and tool definitions. Tools added (25): Tools changed (69):
...and 29 more changed tools. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nder block Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note 🤖 Claude Opus 5.5 responding on behalf of Michael Decisions for this PR:
|
e63cf5b to
ca428c7
Compare
…sandbox metric Merge workflows-sandbox-delivery/sandbox-metric (#610) without a force-push. The block migration moves to 0040, above 0039_sandbox_sender_tenant_state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock layer Merge workflows-sandbox/org-block (#586) without a force-push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is blocked Document the staff block next to the sandbox sender provisioning endpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
PostHog staff have no way to stop the sandbox sender for one abusive organization short of deactivating the whole organization.
Stack: V3, protect shared reputation: #607 → #608 → #609 → #610 → this PR → #587. #587 makes the email worker honor the block, on top of this layer's table.
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)
posthog_organization.workflows-sandbox-senderflag.Changes
SandboxSenderOrganizationBlockhas noteam_id. It is in the IDOR coverage list and in the organization-scoped semgrep rules, because it has an organization foreign key.Warning
The migration is
0040_sandbox_sender_organization_block, on #607's0039_sandbox_sender_tenant_state. When the stack moves upstream, check the number against master on that day.How did you test this code?
Test rationale:
test_sandbox_sender.py, "ensure is not found while the organization is blocked and keeps existing senders". Catches a new project that gets a sandbox sender while its organization is blocked, a block that reaches other organizations, and a blocked project that still gets its existing sender back or loses it. A second organization keeps its own block for the whole test. Red before the service change: the endpoint returned 200.test_sandbox_sender_organization_block_admin.pyposts the real admin add and delete forms as a staff user. It catches a block saved without its author, an admin page staff cannot use, or a delete confirmation that does not name the organization. Dropping the author assignment, or the model's__str__, makes it fail.Live run at
8ee50d709c7, reused for this head. The PR's own change is byte-identical to the one the live run covered, apart from the migration number and its dependency. The ensure action is the same on the old and the new base. The API and admin tests pass on this head against a freshly migrated database.runserverran from clean checkouts against a fully migrated lane database. The steps used session login, the real endpoint, and the real admin forms. The base server wrote the first project's sandbox sender.UI walk at
8d92e19e7bc. A staff user added a block, opened it, and deleted it in the real admin, rendered at 1280x800. The screenshots above come from this walk. Later commits remove comments and restack the same files onto #610.Cross-version. The base email worker ignores a block row and keeps sending. The worker that honors it arrives in #587.
Gaps.
Release status
Docs update
docs/internal/workflows-email-sending-tiers.mddescribes the staff block under sandbox sender provisioning: the admin row, the 404, and that existing senders stay. #587 adds the worker side.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (
claude-opus-5-5)impact,django-migrations,writing-pr-descriptions,pr-description.0040.fleet proofholds the live run and the cross-version run from8ee50d709c7, and the UI walk from8d92e19e7bc, reused for this head with the reason above, plus the prod-shape gap.fleet proof review. Round 1 read the combined commit82d5d74and found three issues, all fixed: the split into this PR and feat(cdp): skip sandbox sends of a blocked organization #587, the cold-cache Node test (now in feat(cdp): skip sandbox sends of a blocked organization #587), and a directsave_modeladmin test (fc8dccdf088). The final read onfc8dccdf088found nothing. After the restack, a final read on the PR's diff against feat(workflows): offer a switch from the sandbox sender #625 found that the block test did not prove organization scoping.99424e33241keeps a block on a second organization in that test, and dropping the organization filter now fails it. The final read on99424e33241found nothing. The gate's judge then asked for a UI walk of the admin. The walk showed "SandboxSenderOrganizationBlock object (7)" on the delete confirmation, so8d92e19e7bcnames the organization there. The judge also flagged code comments against the no-comments rule, and11f1440e84bremoves them. Final reads on both found nothing. After the V3 restack, a final read found that the block test skipped projects that already have a sender, and that the docs waited for feat(cdp): skip sandbox sends of a blocked organization #587.9258b97383acovers both, and the final read on it found nothing.makemigrations --check,hogli product:lint workflows, the IDOR coverage script, and the pre-pushhogli ci:preflight --strict.fleet proof gate: passes on9258b97383a, with the CI gap accepted.0040on #607's0039_sandbox_sender_tenant_state, checked against master when the stack moves upstreamInvolvement: no grilling rounds, one cross-model review round and seven final reads, proof captured.
🤖 Generated with Claude Code