Guide
CLI Mail Merge with 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 all major email providers.
Written by Hazik Director of Product Management
Reviewed 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