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

VerifiedCLI 3.1.1 · Gmail, Outlook · last tested April 11, 2026

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

# 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

Static templates feel generic. Conditional blocks change the message based on the contact’s attributes, making each draft feel hand-written.

#!/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

The review workflow has three stages: generate, review, approve/reject. Here’s how to structure it:

#!/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
done

Schedule approved drafts for optimal delivery

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. After reviewing and approving drafts, schedule them for business hours:

# 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)"
done

Pull conversation context into templates

The best follow-ups reference your last conversation. Pull context from email history before generating the draft:

# 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)

#!/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