Source: https://cli.nylas.com/guides/personalize-outbound-email-cli

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](https://cli.nylas.com/authors/hazik) • Director of Product Management

Reviewed by [Hazik](https://cli.nylas.com/authors/hazik)

Updated April 11, 2026

Verified

 —

CLI

3.1.1

 ·

Gmail, Outlook

 ·

last tested

April 11, 2026

> **TL;DR:** Build a contact CSV, write templates with `${VARIABLE}` syntax, loop through contacts with `nylas email send --yes`. Add `--schedule` for timezone-aware delivery. Always start with `DRY_RUN=true`.

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

File: `outbound_list.csv`

```text
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:

File: `template.sh`

```bash
# 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:

```bash
# 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

File: `personalized-send.sh`

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

```bash
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:

File: `scheduled_send.py`

```python
#!/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](https://cli.nylas.com/guides/auto-create-email-drafts) — generate drafts for human review before delivery
- [Check email deliverability](https://cli.nylas.com/guides/email-deliverability-cli) — verify SPF, DKIM, and DMARC before a send campaign
- [Parse signatures for enrichment](https://cli.nylas.com/guides/enrich-contacts-from-email) — populate merge fields with data extracted from signatures
- [Command reference](https://cli.nylas.com/docs/commands) — every flag, subcommand, and example
