Repository navigation
Description
Map: Map: let teams send a first email before verifying their domain
Spec: Spec: sandbox sender
Cast: GPT-6.1 Sol (Michael, 2026-10-03)
Question
Can the send path block every sandbox send that has a To, CC or BCC address outside the team's verified organization members?
Delivers end to end. Spec section "Send path", "Recipient allowlist":
- An address passes only if it belongs to an active user who is a member of the team's organization and has
is_email_verified = true(legacynulldoes not pass). Compared bare and lower-cased. - Any blocked address blocks the whole send. Never rerouted. A lookup error blocks the send.
- Members are cached per organization for at most one minute.
- Log line names the blocked addresses and says that verifying the project's own domain lifts the restriction (
/writing-user-facing-copy). skipped = true,{ success: false }on the VM stack,workflows sandbox email blockedwithreason = recipient_not_member(orcheck_failed) andblocked_recipient_count, never the addresses.- Applies to test sends and live sends alike.
Test seams. EmailService.executeSendEmail with real Postgres user, organization and membership fixtures, sesV2Client.send mocked. Cases: member To; non-member To; member To with a non-member CC; non-member BCC; unverified member; null verification; member of another organization; lookup failure.
Write scope. A new organization-member lookup service under nodejs/src/cdp/services/managers/, its wiring into EmailService and the CDP hub dependencies, email.service.ts, and tests.
Acceptance criteria
- Each case above behaves as stated, verified through the SES mock and the run log.
- A membership change shows up within a minute.
- Own-sender sends never query organization members.
How to build (map #225)
/tddred-green-refactor, outside-in, at the seams named above. Real Postgres, Valkey and ClickHouse; mock only the SES client (Node and boto3) and the PostHog analytics client.- Repo skills apply where the diff touches their area:
/writing-tests,/improving-drf-endpoints,/django-migrations,/writing-ui-components,/writing-kea-logics,/writing-user-facing-copy,/writing-code-comments. - Public repo: state sandbox rules as product rules. Cap values come from the private note the map points at; never quote it. Fixture addresses use
example.com. - Shipped means a draft PR handed off by
/qa-swarm(see the map's Notes for remotes and branches). Never mark ready, enqueue or merge.
Activity
Silthus commentedon Oct 4, 2026
Note
🤖 GPT-6.1 Sol responding on behalf of Michael
Runtime: T3 Code app-owned child, Codex GPT-6.1 Sol
Task: node:delegated-task:command%3Amcp%3Abaef64af-4783-4753-823f-c9d15c6a1fe6%3Adelegate-task%3Asandbox233-org-recipients-20261004
Child: thread:delegated-task:command%3Amcp%3Abaef64af-4783-4753-823f-c9d15c6a1fe6%3Adelegate-task%3Asandbox233-org-recipients-20261004
Worktree: /home/coder/.t3/worktrees/posthog/sandbox-233-recipients
Branch: feat/workflows-sandbox-recipients
Base: 0007a4f from draft PR PostHog#111401
Cap: four hours, self-deleting deadline wake
CodeRabbit slot: 1 of 3
The sandbox route shipped as a draft with a ready QA handoff. This worker now owns recipient membership checks, real-fixture test-first proof and a fresh draft-PR QA handoff for this ticket alone. Pause and tier-history slices touch the same sending path and will follow serially. Parent owns the map and downstream dispatch; no merge authority.
Silthus commentedon Oct 4, 2026
BLOCKED handoff from GPT-6.1 Sol: draft PR #111483, stacked on #111401, is at
c64bd97aa69269c96932f3ae059b04efc85dafc9.The committed local gate passes 217 tests and five unchanged own-sender snapshots, Node-wide TypeScript, scoped lint/security checks and strict preflight. Final send-method observation captured accepted delivery and whole-send blocking with real organization/member fixtures and mocked external SES/analytics.
The fourth fresh QA round found a valid verified-member quoted address that is incorrectly blocked in CC/BCC display-name syntax. A real-member reproduction confirms this in all four live/test CC/BCC cases. The requested four-round limit stops further fixes and review here, so this issue remains open.
Fork code Actions still require maintainer approval. The latest ancillary reviewer workflow skipped; an earlier zero-job startup failure remains an infrastructure gap. Hosted CodeRabbit reported no actionable comments for the latest increment, but the reproduced QA finding remains.
May this ticket receive one additional fix-and-QA round for quoted-address parsing? The recommendation is yes, while keeping the PR draft until a fresh round clears the fix.
Silthus commentedon Oct 4, 2026
Claim:
impl-233-r5(round 5 of a raised cap of 5)Michael answered the park with "Fix it": one additional bounded fix-and-fresh-QA round is authorized for the quoted-address parser (R4-01). This is round 5 of a raised cap of 5. If round 5 surfaces another new fix item, this ticket hands off blocked without a further fix.
impl-233-r5, starting from headc64bd97aa69.Silthus commentedon Oct 4, 2026
Park:
impl-233-r5, round 5 of 5, blockedThe R4-01 quoted-address fix landed, but round 5 found a new fix item. That makes this round blocked, as agreed, and no further fix was made.
PR #111483 is a draft at
720632842ed.R4-01, fixed in
720632842edName <"a<b>"@example.com>resolves to the member mailbox.c64bd97aa69and pass now.Round 5 panel
Locked decisions
checkDeadlinename, suppression override) moved to follow-ups.Open items
Gaps
One pending question: may I run one more bounded round to add the R5-01 test and pack the R5-02 log rows, followed by a cold QA round? The alternative is to take the PR to review as it is, with both items listed as follow-ups.
Silthus commentedon Oct 4, 2026
Conductor: park verified (comment,
wayfinder:awaiting-human, PR body at720632842ed). Workerimpl-233-r5ended on a provider rate limit after posting it; the lane is retired. CodeRabbit slot 1 released. A successor starts only on Michael's answer.Silthus commentedon Oct 5, 2026
Start:
impl-233-r6, round 6 (raised cap)Michael answered the round 5 park: "Yes, run round 6 and GPT is back."
That authorizes one more bounded fix-and-cold-QA round on PR #111483, starting from
720632842ed.Scope
Nothing else. R5-05 to R5-07 and R4-02 stay follow-ups. If round 6 finds another new fix item, I hand off as blocked and park without fixing it.
Setup
impl-233-r6, Claude Opus 5.5 as lane owner.Silthus commentedon Oct 5, 2026
Park:
impl-233-r6, round 6, blockedBoth round 5 items are fixed, but round 6 found one new fix item. As agreed, that blocks the round, and I made no further fix.
PR #111483 is a draft at
c267c6d8d43.Fixed in
c267c6d8d43Round 6 panel
c267c6d8d43and reported no actionable comments.Locked decisions
Open item
Gaps
One pending question: may I run one more bounded round to add the R6-01 test, followed by a cold QA round? The alternative is to take the PR to review as it is, with R6-01 listed as a follow-up.
Silthus commentedon Oct 5, 2026
Conductor: round 6 park verified (comment,
wayfinder:awaiting-human, PR body atc267c6d8d43, clean tree). Workerimpl-233-r6is retired and CodeRabbit slot 1 is released. A successor starts only on Michael's answer.Silthus commentedon Oct 5, 2026
Start:
impl-233-r7, round 7 (raised cap)Michael answered the round 6 park: "Yeah, go ahead."
That authorizes one more bounded fix-and-cold-QA round on PR #111483, starting from
c267c6d8d43.Scope
check_failedcounts every unchecked recipient. With the deadline guard removed, the new case must fail.The production code stays as it is unless the red run shows a real defect. Nothing else is in scope. R6-02, R5-05 to R5-07 and R4-02 stay follow-ups. If round 7 finds another new fix item, I hand off as blocked and park without fixing it.
Setup
impl-233-r7, Claude Opus 5.5 as lane owner.Silthus commentedon Oct 5, 2026
Resolution:
impl-233-r7, round 7, readyRound 7 came back clean. PR #111483 is a draft at
754efd702a4, ready for Michael's review.Fixed in
754efd702a4(test only)check_failedfor every unchecked recipient. Own senders still bypass the check.Round 7 panel
754efd702a4and reported no actionable comments. Its shared-sender volume note is the activation prerequisite that the parent layer and later tickets own.Calls
Follow-ups
Gaps