DM 2 — Outreach (cold-email drafting)
Turns a scored lead from DM_1 into a send-ready cold email without spending a single model token. All wording lives in one plain-text template; a Python script pulls the chosen business's Notion row and fills the blanks. The model's only job is to run the command and hand you the output.
The offer mirrors DM_1: the core pitch is a website, keyed to the specific gap in
Website Status; booking / chatbot / automation / analytics are add-ons, mentioned
only when the row has that flag.
How to use it — two ways, no flags to type
1. Press it in Notion (fastest). Every lead that has an email has a Draft Email link in its row. Click it and Gmail opens with the subject and body already filled in from the template. Read it, send it. Nothing to install, no command to run.
- These links are generated by
refresh_draft_links.py(below). Re-run that after a prospecting run or after you edit the template.
2. Ask Claude by name.
Say /DM_2_outreach Example Business (or "draft the email for Example Business"). Claude drafts it fresh from the current template and saves it straight to your Gmail drafts, then opens the drafts folder. You just name the business — Claude runs the script for you.
Behind the scenes Claude runs:
python3 draft_email.py "Business Name" --gmail-draft
You never type that. --gmail-draft creates the Gmail draft (never sends) and opens https://mail.google.com/mail/u/0/#drafts; the new draft is at the top. Add --open-stitch (or just ask) to also open Stitch for a mock-up. Without --gmail-draft the script only prints the email for copy-paste.
If several businesses match the name, it takes an exact case-insensitive match, else the first, and says which on stderr.
Keep the Notion "Draft Email" links current
python3 refresh_draft_links.py
Fills the Draft Email column with a one-click Gmail compose link for every lead that has an email, using the current template. Run it after a DM_1 prospecting run, and again whenever you edit email_template.txt (the links bake in the wording, so refresh to update them). No flags, zero tokens.
Prototype in Stitch
There is no URL that spins up a fresh Stitch project on its own — Stitch is behind a Google sign-in and only creates a project from its in-app button. Open Stitch (from the button on the Notion page, or --open-stitch), click Create new project, build the mock-up, then paste the project URL into that lead's Stitch Link column by hand.
Gmail auth
Both Gmail paths use the OAuth credentials in ~/.gmail-mcp/ (set up by the Gmail MCP) and talk to the Gmail API directly with curl — the MCP itself does not need to be connected. If the token can't refresh, the script falls back to printing the email so you can copy-paste. u/0 is the default account; change it in the scripts if the wrong account opens.
The files
| File | Role | Who edits it |
|---|---|---|
email_template.txt | The ONE template: subject, body, sign-off, and the token key. | You (freely) |
draft_email.py | Reads a row, fills the template, prints it, and (with --gmail-draft) saves a Gmail draft. | rarely |
refresh_draft_links.py | Rebuilds the Notion Draft Email click-to-compose links from the current template. | rarely |
One edit, every future draft
email_template.txt is the single source of truth for wording. Change a sentence, the
call-to-action, or the sign-off there and every draft generated after that uses the new
text — nothing is cached, the file is re-read on every run. No code change, no
redeploy, no touching the script. That is the point of keeping the copy in a template
file instead of inside the Python.
Two wording maps live in draft_email.py (not the template) because they are keyed
tables, not free prose:
STATUS_PITCH— the Website Status → gap-pitch lines. These make the same point as DM_1's "Website Status → pitch" table, phrased for email (the email version is a full sentence, DM_1's is a short line you say on a call). If you change the gap you lead with in one, change it in the other so the call and the email agree.ADDON_LINE— the one soft sentence per Add-on Flag.
Editing those is still a one-place change; they're just in the script because each is a lookup table rather than a paragraph.
Token → column map (what the script fills)
| Token | Notion column | Fallback when blank |
|---|---|---|
{business_name} | Business Name (title) | "your business" |
{first_name} | Decision-maker Name (first word) | "there" |
{sector_plural} | Sector (mapped to a natural phrase) | "local businesses" |
{area} | Area | "your area" |
{website_status_pitch} | Website Status → STATUS_PITCH | generic web-value line |
{addon_line} | first Add-on Flag → ADDON_LINE | "" (line closes up) |
{sender_name} | --sender / SENDER_NAME in script | "Your Name" |
{sender_line} | --sig / SENDER_LINE in script | "" (line drops) |
Fallback behaviour:
- Unknown token (typo in the template) is left visible as
{like_this}so you catch it in the draft rather than shipping a hole. - A token that resolves to empty (e.g. no add-on flag) collapses its blank line — no dangling spaces or double blank lines.
- Modern-skip / unknown Website Status → the generic pitch, so the email still sends.
Writing rules baked into the copy (keep them)
The template is deliberately written to not read like AI:
- 4–6 lines, one specific gap, one optional add-on, one soft CTA.
- No em-dashes, no "I hope this finds you well", no rule-of-three lists.
- Plain natural language; contractions; a real question at the end.
- The
STATUS_PITCHandADDON_LINEstrings follow the same rules — if you edit them, keep them dash-free and conversational. Run thehumanizerskill over any new wording.
Notion connection
Same table, token and DB ID as DM_1 (DM_1_prospecting_web_clients). Node is
EPERM-blocked in the Apple sandbox, so this is pure Python + curl. Read-only: DM_2
never writes to Notion — it only fetches the row to draft from. Update Status /
Last Contacted by hand (or via DM_1's helpers) after you send.
What not to do
- Don't draft emails token-by-token with the model — that's the whole thing this avoids.
- Don't scatter copy across the script; wording belongs in
email_template.txt. - Don't let
STATUS_PITCHdrift from DM_1's pitch table — the call and email must agree. - Don't add a second template file; one editable template, one place.