My AI Drafts My Follow-Up Emails Every Morning. I Still Press Send. A developer extended a custom MCP server so that Claude drafts follow-up sales emails to leads every weekday morning, but deliberately gave the agent no send capability — drafts are written to files and the user reviews and presses send manually. The build required one new tool, save_draft, plus a schema fix adding an email column to the existing SQLite leads table via an ALTER TABLE statement, since the original lead tracker stored names, companies, notes and follow-up dates but no email addresses. The author framed the missing send tool as a hard safety boundary: "A prompt is a request. A missing tool is a wall. You know the feeling. A lead goes quiet, you open a blank email, stare at it for a minute, and push it to tomorrow. Here’s the thing. Knowing who to email is rarely the problem. Actually writing the email is. In part one, I built a tiny MCP server that lets Claude read my leads. It can tell me who’s due for a follow-up. But it can’t help with the bit everyone avoids. So this time I gave it one more job. Every weekday morning, it drafts the follow-ups. I read them, fix a line, and press send myself. You don’t need part one to follow along. I’ll show every change. It took one new tool and a short script. Plus one slightly embarrassing fix to my own code. Let’s be honest. The tempting version is a bot that emails your leads on its own while you sleep. I didn’t build that. On purpose. A follow-up goes to someone who already gave you their time. Maybe they asked about pricing. Maybe they said “ping me after Diwali.” That context is the whole email. If the model gets it wrong, you don’t get a second first impression. And models do get it wrong. Sometimes they invent a detail that sounds right, like a meeting you never had. In a draft, that’s a two-second fix. In a sent email, it’s an apology. So the system has one hard line: The agent can write as many drafts as it likes. It cannot send a single email. That isn’t a rule in the prompt. It’s a rule in the code. There is no send tool, so there’s nothing to misuse. Here’s why it matters. A prompt is a request. A missing tool is a wall. I’ll own this one. The lead tracker from part one stores names, companies, notes and follow-up dates. It doesn’t store email addresses. Yes, really. I built a follow-up system with no way to follow up. Here’s the fix. Add three lines to the end of db in leads server.py, just before return conn: cols = row 1 for row in conn.execute "PRAGMA table info leads " if "email" not in cols: conn.execute "ALTER TABLE leads ADD COLUMN email TEXT" It checks the table and adds the column only if it’s missing. Your existing leads stay put. New and old databases both just work. Then let add lead take an email: python @mcp.tool def add lead name: str, email: str = "", company: str = "", follow up: str = "", notes: str = "" - str: """Add a new lead. follow up is a date like 2026-10-08.""" with db as conn: cur = conn.execute "INSERT INTO leads name, email, company, follow up, notes VALUES ?, ?, ?, ?, ? ", name, email or None, company, follow up or None, notes , return f"Added lead {cur.lastrowid}: {name} {company} " Your old leads still have no address, though. So let update lead set one too. Replace it with this version: python @mcp.tool def update lead lead id: int, status: str = "", follow up: str = "", note: str = "", email: str = "" - str: """Update a lead's status, follow-up date or email address, or append a note.""" with db as conn: lead = conn.execute "SELECT FROM leads WHERE id = ?", lead id, .fetchone if not lead: return f"No lead with id {lead id}" conn.execute "UPDATE leads SET status = ?, follow up = ?, notes = ?, email = ? WHERE id = ?", status or lead "status" , follow up or lead "follow up" , lead "notes" + "\n" + note .strip if note else lead "notes" , email or lead "email" , lead id, , return f"Updated lead {lead id}" Now you can just tell Claude “Dr. Patel’s email is patel@smiledental.com” and it fills the gap. This is the only new tool. It writes a draft to a file. That’s it. Add from email.message import EmailMessage to your imports, plus this line under DB: DRAFTS = Path file .parent / "drafts" Then add the tool, just above the if name == " main ": line at the bottom: php @mcp.tool def save draft lead id: int, subject: str, body: str - str: """Save a follow-up email for this lead as a draft file for a human to review. This never sends anything. Plain text, under 120 words, no links.""" with db as conn: lead = conn.execute "SELECT FROM leads WHERE id = ?", lead id, .fetchone if not lead: return f"No lead with id {lead id}" if not lead "email" : return f"Lead {lead id} has no email address. Skipped." msg = EmailMessage msg "To" = lead "email" msg "Subject" = subject msg "X-Unsent" = "1" Outlook opens this as an editable draft msg.set content body DRAFTS.mkdir exist ok=True path = DRAFTS / f"{date.today .isoformat }-lead-{lead id}.eml" path.write bytes bytes msg return f"Draft saved: {path.name}" Two small choices here do a lot of work. The model never types the email address. It passes a lead id, and the tool looks up the address itself. So the agent can't invent one, or send Dr. Patel's email to Dr. Mehta. The X-Unsent: 1 header. Double-click the .eml file in Outlook and it opens as a draft you can edit, not a received message. In Apple Mail, open it and use Message Send Again , which gives you an editable copy. Either way, you tweak a line and press send yourself. Create morning.py next to your server: python import asyncioimport sysfrom pathlib import Path python from dotenv import load dotenvfrom langchain.agents import create agentfrom langchain mcp adapters.client import MultiServerMCPClient HERE = Path file .parentload dotenv HERE / ".env" cron doesn't see your shell's environment MODEL = "anthropic:claude-sonnet-5-5" any tool-calling model works RULES = """You prepare my follow-up emails each morning. 1. Call due follow ups.2. For each lead, read their notes and write ONE short follow-up.3. Save it with save draft. Never claim you sent anything. Style: plain English, under 120 words, one clear question at the end.Mention something specific from the notes. No "just checking in".If a lead has no notes, skip them and say so.""" python async def main : client = MultiServerMCPClient { "leads": { "command": sys.executable, "args": str HERE / "leads server.py" , "transport": "stdio", } } tools = await client.get tools agent = create agent MODEL, tools, system prompt=RULES result = await agent.ainvoke {"messages": {"role": "user", "content": "Prepare today's follow-ups."} } print result "messages" -1 .text if name == " main ": asyncio.run main Install the extras with pip install langchain langchain-mcp-adapters langchain-anthropic python-dotenv. Then put your key in a .env file in the same folder: ANTHROPIC API KEY=your-key-here Run python morning.py once by hand. You should see a short summary, plus fresh .eml files in a drafts folder. Notice sys.executable. It means "the same Python that's running this script." Remember the full-path headache from part one? This quietly solves it. Whatever Python runs morning.py also runs the server. On a Mac or Linux machine, open your crontab with crontab -e and add one line: 0 9 1-5 /full/path/leads-mcp/.venv/bin/python /full/path/leads-mcp/morning.py /full/path/leads-mcp/morning.log 2 &1 That runs it at 9am, Monday to Friday, and keeps a log so you can see what happened. On a Mac, if your folder lives in Documents, Desktop or Downloads, give cron Full Disk Access in System Settings Privacy & Security . Otherwise macOS quietly blocks it from reading your files. On Windows, Task Scheduler does the same job. Point it at the same Python and script. Cron runs in an empty room. It doesn’t load your shell, so your API key isn’t there. The script works perfectly when you run it by hand, then fails silently at 9am. That’s why morning.py loads .env itself. If something breaks, check morning.log first. A sleeping laptop misses the job. If your Mac is closed at 9am, cron simply skips that day. If that bothers you, use launchd instead. It runs a missed job when the machine wakes up. Or put the whole folder on a tiny cloud server and forget about it. Generic notes give you generic emails. This one isn’t a code problem. If a lead’s note just says “interested,” the model has nothing to work with, and you get the dreaded “just checking in.” That’s why the prompt tells it to skip leads with no notes. The fix is upstream: write one specific line after every call. “Wants it live before Diwali” turns into a far better email than “follow up.” The agent didn’t save me from writing emails. It saved me from the blank page . Opening a draft that’s 80% right and fixing one sentence is a different job from starting cold. It’s the difference between editing and writing. Most mornings, editing wins. And because nothing sends without me, I trust it. That trust is the part most automation skips. You can’t scale something you’re nervous about. Here’s what actually works: let the AI do the part you procrastinate on, and keep the part where your name is on the line. Where do you draw the line? Would you let an agent send emails to your leads without checking them first? I’m curious where people land on this. Tell me in the responses. If you missed part one, it’s here . It’s where the lead tracker comes from. Follow me if you want part three. I’m thinking of giving this agent a memory of past emails, so it never repeats itself. My AI Drafts My Follow-Up Emails Every Morning. I Still Press Send. https://pub.towardsai.net/my-ai-drafts-my-follow-up-emails-every-morning-i-still-press-send-049cf4bdb06a was originally published in Towards AI https://pub.towardsai.net on Medium, where people are continuing the conversation by highlighting and responding to this story.