Guide
Automate Email Draft Creation and Review
Direct sends can't be undone. One poorly personalized email to a VP burns a relationship. Drafts give you a review gate: generate 50 personalized messages from templates with conditional logic, route them through a review workflow, then send only the approved ones. Works across all major email providers.
Written by Prem Keshari Senior SRE
Reviewed by Caleb Geene
Why drafts are the safety net for automation
According to HubSpot’s 2024 Sales Engagement Report, 23% of automated outbound emails contain personalization errors (wrong name, broken merge fields, incorrect company). Those errors cost deals. Drafts insert a human review step between generation and delivery.
The workflow: template + data = drafts. Review drafts. Send the approved ones. Delete the rest. Same efficiency as direct sends, but with a safety net that catches the 23%.
Create a single draft
A single draft is an email that exists in your provider’s Drafts folder without being sent. The Nylas CLI creates one in under 2 seconds across Gmail, Outlook, and IMAP providers using nylas email drafts create. You specify the recipient, subject, and body as flags. The draft stays in your mailbox until you explicitly send or delete it, so you can review the content before it goes out.
After creating a draft, use nylas email drafts list to see all drafts, nylas email drafts show to inspect one by ID, and nylas email drafts send to deliver it. This three-step flow (create, inspect, send) takes about 10 seconds total and replaces manual copy-paste into a compose window.
# Create a draft (appears in your Drafts folder)
nylas email drafts create \
--to "sarah@acme.com" \
--subject "Following up on our conversation" \
--body "Hi Sarah — wanted to circle back on the API integration timeline."
# List, inspect, then send
nylas email drafts list
nylas email drafts show <draft-id>
nylas email drafts send <draft-id>Build templates with conditional content blocks
Conditional content blocks are template sections that change based on contact attributes like deal stage, days since last contact, or job title. According to Litmus’s 2024 State of Email report, emails with dynamic content blocks see 29% higher click-through rates than static templates. Each block evaluates a contact field and swaps in the right paragraph, so 50 drafts can look like 50 hand-written messages.
The Python script below reads a CSV of contacts and generates one draft per row using nylas email drafts create. It picks a different opening line based on how many days have passed since your last conversation, and a different call-to-action based on deal stage (discovery, proposal, or negotiation). The output is a JSON manifest listing every draft ID and recipient, which feeds into the review workflow.
#!/usr/bin/env python3
"""Generate drafts with conditional content blocks."""
import csv
import subprocess
from dataclasses import dataclass
@dataclass
class Contact:
email: str
name: str
company: str
title: str
last_subject: str
days: int
deal_stage: str # "discovery", "proposal", "negotiation"
def build_body(c: Contact) -> str:
"""Build email body with conditional sections."""
greeting = f"Hi {c.name},"
# Conditional opening based on days since last contact
if c.days <= 7:
opening = f"Great catching up last week about {c.last_subject}."
elif c.days <= 30:
opening = (f"It's been {c.days} days since we discussed "
f"{c.last_subject}. Wanted to keep things moving.")
else:
opening = (f"It's been a while since our conversation about "
f"{c.last_subject}. Hope things are going well at {c.company}.")
# Conditional CTA based on deal stage
if c.deal_stage == "discovery":
cta = "Would you have 15 minutes this week to explore this further?"
elif c.deal_stage == "proposal":
cta = ("I've put together a proposal based on our discussion. "
"Can I walk you through it on a quick call?")
elif c.deal_stage == "negotiation":
cta = ("I'd love to finalize the details. "
"When works for a 30-minute call with your team?")
else:
cta = "Would you have time for a quick call this week?"
return f"{greeting}\n\n{opening}\n\n{cta}\n\nBest,\nYour Name"
def create_draft(c: Contact) -> str:
"""Create a draft via Nylas CLI and return the draft ID."""
body = build_body(c)
result = subprocess.run(
["nylas", "email", "drafts", "create",
"--to", c.email,
"--subject", f"Following up on {c.last_subject}",
"--body", body, "--json"],
capture_output=True, text=True, check=True,
)
return result.stdout.strip()
# Generate drafts from CSV
with open("outbound_list.csv") as f:
reader = csv.DictReader(f)
manifest = []
for row in reader:
contact = Contact(
email=row["email"], name=row["name"],
company=row["company"], title=row["title"],
last_subject=row["last_subject"],
days=int(row["days"]), deal_stage=row["deal_stage"]
)
draft_output = create_draft(contact)
manifest.append({"email": contact.email, "draft": draft_output})
print(f"Draft created for {contact.name} ({contact.email})")
# Save manifest for review workflow
import json
with open("draft_manifest.json", "w") as f:
json.dump(manifest, f, indent=2)
print(f"\nCreated {len(manifest)} drafts. Review manifest: draft_manifest.json")Build a review workflow
A draft review workflow is a three-stage process: generate drafts, inspect each one, then approve or reject. Salesforce’s 2024 State of Marketing report found that teams using an approval step before sending reduced email error rates by 38%. The script below lists every draft in your mailbox, shows a preview of each body, and prompts you to send, delete, view in full, or skip.
This Bash script pipes nylas email drafts list --json through jq to extract the draft ID, recipient, and subject. For each draft, it calls nylas email drafts show to display the first 5 lines of the body. You choose an action at the prompt: “s” sends immediately, “d” deletes the draft, “v” shows the full body with a send confirmation, and any other key skips to the next draft.
#!/bin/bash
# review-drafts.sh — interactive review of generated drafts
echo "=== Draft Review Workflow ==="
echo ""
# List all drafts with recipients
nylas email drafts list --json | jq -r '
.[] | "\(.id)\t\(.to[0].email)\t\(.subject)"
' | while IFS=$'\t' read -r id email subject; do
echo "---"
echo "To: $email"
echo "Subject: $subject"
echo "Draft: $id"
echo ""
# Show preview of the body
nylas email drafts show "$id" --json | jq -r '.body' | head -5
echo "..."
echo ""
read -p "Action [s]end / [d]elete / [v]iew full / [n]ext: " action
case "$action" in
s) nylas email drafts send "$id"
echo "SENT to $email" ;;
d) nylas email drafts delete "$id"
echo "DELETED draft for $email" ;;
v) nylas email drafts show "$id" --json | jq -r '.body'
read -p "Send this? [y/n]: " confirm
[[ "$confirm" == "y" ]] && nylas email drafts send "$id" ;;
*) echo "Skipped" ;;
esac
doneSchedule approved drafts for optimal delivery
Scheduled sending is a delivery strategy where approved drafts are queued for a specific time rather than sent immediately. According to Mailchimp’s Send Time Optimization data (2024), emails sent between 9–11 AM in the recipient’s local timezone get 14% higher open rates. You can stagger sends across a 2-hour window to avoid spam filter triggers from burst sending.
The script below takes an array of approved draft IDs and sends each one at 5-minute intervals starting at 9:00 AM the next day. It uses nylas email drafts send with the --schedule flag to set the delivery time. For a batch of 24 drafts, the full run takes about 2 hours of staggered delivery.
# Send approved drafts on a schedule (stagger across 2 hours)
APPROVED_IDS=("draft_001" "draft_002" "draft_003")
for i in "${!APPROVED_IDS[@]}"; do
id="${APPROVED_IDS[$i]}"
offset_minutes=$(( i * 5 ))
nylas email drafts send "$id" \
--schedule "tomorrow 9:$(printf '%02d' $offset_minutes)am"
echo "Scheduled: $id (9:$(printf '%02d' $offset_minutes) AM)"
donePull conversation context into templates
Conversation context pulling is the process of retrieving your last email thread with a contact so follow-up drafts reference specific details. According to Yesware’s 2023 outreach study, follow-up emails that reference the previous conversation’s subject line see a 22% higher reply rate than generic follow-ups. This technique prevents the “just checking in” trap.
The script below uses nylas email list --json with a jq filter to find the most recent message involving a specific contact. It extracts the subject, date, and a 200-character snippet from the email body. You can feed these values into your template script to produce drafts that feel like natural continuations of real conversations.
# Find the last email thread with a contact
LAST=$(nylas email list --json --limit 50 | jq -r '
[.[] | select(.from[0].email == "sarah@acme.com"
or (.to[]?.email == "sarah@acme.com"))]
| sort_by(.date) | last')
SUBJECT=$(echo "$LAST" | jq -r '.subject')
DATE=$(echo "$LAST" | jq -r '.date')
SNIPPET=$(echo "$LAST" | jq -r '.snippet // ""' | head -c 200)
echo "Last conversation: $SUBJECT"
echo "Preview: $SNIPPET"Batch draft creation (Bash)
Batch draft creation is the process of generating multiple personalized email drafts from a CSV file in a single script run. A Bash-only approach avoids the Python dependency and runs on any system with a POSIX shell. This script processes one row per iteration, applies conditional opening lines based on the “days since last contact” field, and reports a running count as it goes.
The script below reads outbound_list.csv line by line, skipping the header row. For each contact, it picks an opening sentence (recent, mid-range, or stale) and calls nylas email drafts create with the personalized body. Processing 50 contacts takes roughly 90 seconds depending on network latency. After the loop finishes, it prints the total count and reminds you to review with nylas email drafts list.
#!/bin/bash
# batch-drafts.sh — create personalized drafts from CSV
CREATED=0
while IFS=, read -r email name company title last_subject days deal_stage; do
[[ "$email" == "email" ]] && continue # skip header
# Build conditional body
if [ "$days" -le 7 ]; then
opening="Great catching up last week about $last_subject."
elif [ "$days" -le 30 ]; then
opening="It's been $days days since we discussed $last_subject."
else
opening="It's been a while since our $last_subject conversation."
fi
body="Hi $name,
$opening
Would you have time this week for a quick call?
Best,
Your Name"
nylas email drafts create \
--to "$email" \
--subject "Following up on $last_subject" \
--body "$body"
CREATED=$((CREATED + 1))
echo "[$CREATED] Draft for $name ($email)"
done < outbound_list.csv
echo "Done. Created $CREATED drafts."
echo "Review: nylas email drafts list"Next steps
Once your draft workflow handles batch creation and review, you can extend it with direct sending, contact enrichment, and autocomplete. The Nylas CLI supports over 40 email-related subcommands across drafts, messages, and contacts. Start with the resources below to build on what you’ve set up here.
- Personalize outbound email — when you’re ready to skip the review step and send directly with scheduling and throttling
- Build email autocomplete — speed up manual composition with fuzzy contact search from your shell
- Parse signatures for enrichment — extract job titles and company info to improve conditional template targeting
- Command reference — every flag, subcommand, and example
- RFC 9051 -- IMAP4rev2 — the “\Draft” flag and Drafts mailbox semantics
- Gmail API: Users.drafts reference — canonical draft create / update / send endpoints
- Microsoft Graph: Message resource — the “isDraft” property and corresponding draft endpoints in Outlook