# CLI mail merge with send-time optimization

Source: https://developer.nylas.com/docs/cookbook/cli/mail-merge/

A "mail merge" worth shipping has three properties: it actually personalizes (not just `Hi $FNAME`), it respects each recipient's timezone so the message lands during their morning, and it doesn't burn your sender reputation by firing 5,000 messages in 12 seconds. This recipe is the CLI version of that — a CSV, a template with `${VAR}` placeholders, and `nylas email send` in a loop with sane throttling and a dry-run mode that's on by default.

## The CSV

`outbound_list.csv`:

```csv
email,name,company,title,last_subject,days_since_contact,timezone
ada@acme.test,Ada,Acme,VP Eng,Q1 review,42,America/Los_Angeles
rin@globex.test,Rin,Globex,CTO,Renewal,17,Europe/Berlin
```

The columns become variables in the template. Add as many as your personalization needs.

## The template

Use `${VAR}` placeholders and let `envsubst` interpolate them per row:

```text
Hi ${name},

It's been ${days_since_contact} days since we last connected on
"${last_subject}". I'd love to do a 20-minute check-in with you and
the ${company} team.

How does ${suggested_time} ${timezone} look?

— Sam
```

## The send loop (bash)

```bash
#!/usr/bin/env bash
set -euo pipefail

DRY_RUN="${DRY_RUN:-true}"          # safe default
TEMPLATE="$(cat ./template.txt)"

tail -n +2 ./outbound_list.csv | while IFS=, read -r email name company title last_subject days_since_contact timezone; do
  export name company title last_subject days_since_contact timezone
  body="$(envsubst <<< "$TEMPLATE")"
  schedule="tomorrow 9am ${timezone}"

  if [[ "$DRY_RUN" == "true" ]]; then
    echo "→ would send to $email at $schedule"
    continue
  fi

  nylas email send \
    --to "$email" \
    --subject "Catching up on ${last_subject}" \
    --body "$body" \
    --schedule "$schedule" \
    --yes \
    --json >> sent.log

  sleep 5    # baseline throttle
done
```

Run it dry first:

```bash
bash personalized-send.sh
```

When the output looks right, ship it for real:

```bash
DRY_RUN=false bash personalized-send.sh
```

## Why these defaults exist

- **`--schedule "tomorrow 9am ${timezone}"`** — Mailchimp's analysis (Feb 2024 update) shows mail delivered between 9–11 AM in the recipient's local timezone gets ~14% better open rates than uniform send times. The flag does the math for you.
- **`sleep 5`** — providers detect "volume spikes" and drop your reputation when you blast at machine speed. Five seconds between sends is a reasonable floor for warmed-up accounts. New accounts should crawl: 10/day, then double weekly.
- **`DRY_RUN=true` by default** — sends are irreversible. Make them require an explicit opt-out.

## Distribute the load

Even with `sleep`, scheduling all sends at the same minute across hundreds of recipients still creates a spike at the provider. Stagger with `--schedule` over a 2–3 hour window:

```bash
HOUR=$((9 + RANDOM % 3))   # 9, 10, or 11
schedule="tomorrow ${HOUR}:$((RANDOM % 60)) ${timezone}"
```

This produces a smooth distribution per timezone instead of a thundering herd at exactly 9:00.

## Test before you trust

Before the real run, retarget the first three rows to your own address:

```bash
head -n 4 outbound_list.csv |
  awk -F, 'NR==1 || $1="you@example.com"' OFS=, > preview.csv
DRY_RUN=false ./personalized-send.sh ./preview.csv
```

Confirm the rendered bodies, then run the full list.

## Things to know

- **`envsubst` is shell-safe but variable-naïve.** Quote your template carefully and avoid characters that look like shell substitutions.
- **`--track-label "spring-outreach-2026"`** lets you slice opens and clicks per campaign without inferring from subject lines later.
- **Hard cap.** Microsoft 365 is ~10,000/day per mailbox; Gmail is ~2,000/day; Exchange depends on tenant policy. The CLI surfaces 429s as exit code 1 — back off with `sleep 60` and retry.

## Next steps

- [Run the merge through an MCP-connected agent](/docs/cookbook/cli/llm-agent-with-tools/)
- [Nylas CLI](https://cli.nylas.com/) — installation and command reference