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

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
done

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

Pull 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.