Guide
CLI Mail Merge: Variable Substitution and Send-Time Optimization
GUI mail merge tools lock you into per-seat pricing and can't integrate with data pipelines. The Nylas CLI lets you run mail merge from a script: variable substitution from enriched contact data, conditional content blocks, timezone-aware scheduling, and throttled sends. Works across Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP.
By Hazik
Build the contact CSV with merge fields
The foundation is a CSV that contains more than just email addresses. You need enough context to make each message feel hand-written:
email,name,company,title,last_subject,days_since,timezone
sarah@acme.com,Sarah Chen,Acme Corp,VP Engineering,API integration timeline,14,America/Los_Angeles
bob@globex.com,Bob Martinez,Globex Inc,Head of Platform,Q2 roadmap review,7,America/New_York
yuki@techco.jp,Yuki Tanaka,TechCo,Engineering Manager,SDK performance,21,Asia/TokyoVariable substitution in templates
Templates use ${VARIABLE} syntax. Single quotes prevent premature shell expansion:
# Template with merge variables
TEMPLATE='Hi ${NAME},
I wanted to follow up on our conversation about ${LAST_SUBJECT}.
Given your role as ${TITLE} at ${COMPANY}, I think there is a good fit.
Would you have 15 minutes this week to discuss?
Best,
Your Name'
# Render with envsubst
NAME="Sarah" COMPANY="Acme Corp" TITLE="VP Engineering" \
LAST_SUBJECT="API integration timeline" \
envsubst <<< "$TEMPLATE"Subject line patterns that get opened
According to Yesware’s 2024 Email Analysis (5 million sales emails), subject lines with the recipient’s company name get 22% higher open rates:
# Reference last conversation (37% reply rate per Yesware)
SUBJECT="Re: ${LAST_SUBJECT}"
# Company-specific (22% higher open rate)
SUBJECT="Quick question for ${COMPANY}'s ${TITLE}"
# Time-based urgency
SUBJECT="Following up — ${DAYS} days since we spoke"The dry-run send loop
#!/bin/bash
# personalized-send.sh — mail merge with dry-run safety
DRY_RUN=${DRY_RUN:-true}
DELAY=5
while IFS=, read -r email name company title last_subject days tz; do
[[ "$email" == "email" ]] && continue
subject="Following up on ${last_subject}"
body="Hi ${name},
I wanted to circle back on ${last_subject}. As ${title} at ${company}, you mentioned some challenges I think we can help with.
Would you have 15 minutes this week?
Best,
Your Name"
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY RUN] To: ${email} | Subject: ${subject}"
else
nylas email send --to "$email" --subject "$subject" --body "$body" --yes
echo "Sent: ${email}"
sleep "$DELAY"
fi
done < outbound_list.csvDRY_RUN=true bash personalized-send.sh # preview
DRY_RUN=false bash personalized-send.sh # sendTimezone-aware send-time optimization
According to Mailchimp’s Send Time Optimization data (2024), emails delivered between 9-11 AM in the recipient’s local timezone get 14% higher open rates:
#!/usr/bin/env python3
"""Send personalized emails with timezone-aware scheduling."""
import csv, subprocess, sys, time
DRY_RUN = "--send" not in sys.argv
DELAY = 5
with open("outbound_list.csv") as f:
for i, row in enumerate(csv.DictReader(f)):
subject = f"Following up on {row['last_subject']}"
body = (f"Hi {row['name']},\n\n"
f"I wanted to circle back on {row['last_subject']}. "
f"As {row['title']} at {row['company']}, you mentioned "
f"some challenges I think we can help with.\n\n"
f"Would you have 15 minutes this week?\n\nBest,\nYour Name")
if DRY_RUN:
print(f"[DRY RUN] To: {row['email']} | TZ: {row['timezone']}")
else:
subprocess.run([
"nylas", "email", "send",
"--to", row["email"], "--subject", subject,
"--body", body, "--schedule",
f"tomorrow 9am {row['timezone']}", "--yes",
], check=True)
print(f"Scheduled ({i+1}): {row['email']} at 9am {row['timezone']}")
time.sleep(DELAY)
if DRY_RUN:
print("\nDRY RUN — pass --send to actually send")Throttling and deliverability safeguards
According to Google’s Email Sender Guidelines (February 2024), sudden volume spikes trigger reputation penalties:
- 5-second minimum delay between sends. Providers flag rapid-fire sending.
- Stagger delivery across 2-3 hours with
--schedule. - Warm up new accounts over 2-4 weeks. Start at 10/day, add 10/day per week.
- Review the first 3-5 by sending to yourself before the full batch.
- Monitor bounces:
nylas email search "delivery failure" --json
Next steps
- Automate draft creation — generate drafts for human review before delivery
- Check email deliverability — verify SPF, DKIM, and DMARC before a send campaign
- Parse signatures for enrichment — populate merge fields with data extracted from signatures
- Command reference — every flag, subcommand, and example