SOP: Email Send Workflow
SOP: Email Send Workflow
Purpose
Standard process for composing and sending branded emails to Westside parents. Used by agents (Ava, Penny, dev agents) and humans (Lucas, Marcus). Applies to all email sends — blasts, transactional, and one-off. Produces a sent email with EmailLog audit trail, or a clear "not approved" stop. The key outcome is that new emails can be sent without code changes when the layout and audience query already exist.
Steps
- Intake. Requester (Marcus or Lucas) describes email intent. Composer (agent) determines:
- Draft content. Composer writes the email data dict and presents it to Approver (Lucas) in the conversation. Include: layout, email_type, query, subject (with per-recipient
{{placeholders}}), data dict (headline, body, cta_text, cta_url, footer_note). Do NOT call any endpoint yet. - Approver reviews draft text. Approver says "looks good" or requests changes. If changes requested, revise and re-present. Loop until text is approved.
- Send test email. Call
POST /email/blastwith the draft payload plus"test_email": "draneylucas@gmail.com". Report: "Test email sent to draneylucas@gmail.com. Check your phone." - STOP — wait for approval. Do NOT proceed. Approver checks email on their phone (real Gmail renderer, real mobile viewport). Approver responds:
- Confirm blast scope. Query the audience count: report "Sending to N parents. Confirm?" Wait for explicit "send it" or "yes" from Approver.
- Blast. Call
POST /email/blastwithouttest_emailparam. Report results: "Sent N emails. M errors: [details if any]." - Verify. Confirm EmailLog entries via database query or admin endpoint. Report send count matches expected audience.
Contract Emails (Enhanced Gate)
Contract-related emails (contract_offer, contract_reminder) require double approval:
- Steps 1-5 as above (first approval after test email on phone).
- Approver says "approved" after checking test on phone.
- Show recipient list and count. Approver says "send it" (second approval).
- Send ONE real email to a single parent (not test_email — a real recipient). Approver verifies in that parent's Gmail if possible, or confirms intent.
- Approver says "yes, blast the rest" (third confirmation). Then blast.
New Email Type Checklist
If the email needs infrastructure that doesn't exist:
- New audience query needed? → Create Forgejo issue + board item for
email_queries.pychange. - New EmailType enum value needed? → Create Forgejo issue + board item for alembic migration.
- New layout needed? → Create Forgejo issue + board item for MJML template + compile.
- All three exist? → No code needed. Proceed directly to Step 1.
Rules
- NEVER send to real recipients without explicit approval in the current conversation. Approval from a previous session does not carry over.
- ONE APPROVAL = ONE SEND. Every individual send (test or blast) requires its own explicit 'send it' / 'yes' in the conversation immediately before that send. Prior approvals do NOT authorize subsequent sends, even minutes apart. A new tool call = a new send = a new approval required.
- CONTENT CHANGES DO NOT AUTHORIZE A SEND. 'Change the date to Friday' or 'fix the wording' is instruction to modify the draft, NOT to send it. After applying the change, re-present the draft and wait for explicit send approval. 2026-04-13 incident: one approval was treated as a blanket, resulting in 3 unapproved sends to a real recipient.
- NEVER skip the test email step. Every email send starts with
test_email=draneylucas@gmail.com. - NEVER create audience queries or layouts inline during a send workflow. Missing infrastructure = code ticket first.
- NEVER assume approval. 'Looks good' on the draft text is NOT approval to blast. The phone check (Step 5) is the gate.
- When in doubt, ask. The cost of asking 'send this now?' is 2 seconds. The cost of an unapproved send is trust erosion and potential inbox contamination for real recipients.
- Contract emails require double approval — see enhanced gate above.
- Test email address:
draneylucas@gmail.com(Lucas's personal Gmail for mobile verification). For Marcus-facing tests, usemarcusdraney23@gmail.comONLY with explicit approval for each send. - The browser is not the truth. Gmail's renderer is proprietary. Only real emails on real phones count as verification.
- EmailLog is automatic — do not skip or manually log. Every
send_templated_email()call writes toemail_log.
Related
arch-email— architecture reference: components, layouts, preview workflow, design decisionsproject-westside-basketball— parent projectsop-board-workflow— board workflow for creating tickets when infrastructure is missing