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/Tokyo

Variable 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.csv
DRY_RUN=true bash personalized-send.sh    # preview
DRY_RUN=false bash personalized-send.sh   # send

Timezone-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