Evergreen Mail Merge
Send personalized emails from an Excel or CSV list, each with its own attachment, through Outlook or Gmail.
Statements to clients, receipts to donors, certificates to attendees, invoices, reminders: write the message once with fields like {{First Name}}, point it at your list and a folder of files, and each person gets their own email with their own file attached.
- Each person gets their own attachment. List file names in a column, or use a pattern like
{{Account}}*.pdfto pick each person's file from a folder automatically. Several attachments per email are fine. - Personalized subject and message. Any column in your list can go in the subject or message.
{{First Name|there}}uses "there" when the first name is blank. - Checks everything first. Before anything is sent, it shows every email in your browser and lists any problems: a misspelled address, a missing file, an attachment that's too big. Emails with problems are held back; the rest can go.
- Test on yourself. Send the first few to your own address, marked [TEST] and showing who the real recipient would be.
- Sends through Outlook or Gmail. Classic Outlook sends from your own account, and can save everything to Drafts instead so you can review before sending. Gmail and other email services work with an app password, stored encrypted.
- Never sends twice. Every email is recorded as it goes. If a run stops (a lost connection, a daily sending limit), run it again and only the ones that didn't go out are sent.
- Nothing to install. It runs on PowerShell, which is built into Windows 10 and 11. Excel doesn't need to be installed.
Quick start (about 15 minutes)
- Unblock and unzip. Right-click the ZIP you downloaded, choose Properties, tick Unblock if it's shown, click OK, then right-click → Extract All. Put the folder somewhere short and permanent, like
C:\MailMerge. - Try the sample. Double-click Mail Merge.bat and choose 1. You'll see a preview of sample statements in your browser, including two with problems, so you can see how they're reported. Nothing is sent.
- Set up your list. Use an Excel workbook or CSV file with a header row: one column for email addresses, plus any columns you want in the message (names, amounts, dates, account numbers).
- Write your message. Choose 7 and edit
message.txtin Notepad. Use{{Column Name}}wherever a value from the list should go. - Edit the settings. Choose 6 and set your list, subject and attachments in
merge.json. Every option is explained below. - Choose how to send. Follow
EMAIL-SETUP.md. With classic Outlook there's nothing else to do. - Check, test, send. Choose 1 to check every email, 2 to send a few to yourself, then 3 to send to everyone (or 4 to save them to Outlook Drafts).
Settings (merge.json)
| Setting | What it does | Example |
|---|---|---|
name | A name for this mail merge. It's how the program remembers who has already been sent. Use a new name for each new batch, like "November statements". | "October statements" |
list | Your Excel (.xlsx) or CSV list. A full path, or a path inside this folder. | "C:\\Clients\\october.xlsx" |
sheet | Which sheet of the workbook to use. Leave out for the first sheet. | "Clients" |
email_column | The column with email addresses. Several addresses in one cell, separated by ;, all receive the email. | "Email" |
cc_column | Optional column of addresses to copy. | "Assistant Email" |
bcc | Optional address that gets a blind copy of every email, for your records. | "records@yourbusiness.com" |
subject | The subject line. Fields work here too. | "Your {{Month}} statement" |
message | The message file. A .txt file is easiest (see below). An .html file works for designed emails. | "message.txt" |
attachments_folder | Where the attachments are. | "C:\\Statements\\October" |
attachment_column | Optional column with each person's file name(s). Separate several with ;. * works as a wildcard. | "Attachment" |
attachment_pattern | Optional pattern that finds each person's file, built from their row. | "{{Account}}*.pdf" |
missing_attachment | "skip" (the default) holds back anyone whose file is missing. "send-without" sends to them anyway, without it. | "skip" |
max_attachment_mb | Holds back emails whose attachments add up to more than this. Most providers refuse emails over 20 to 25 MB. | 20 |
send_column | Optional column that says who to send to. Rows that are blank, or say No, are skipped. | "Send" |
test_to | Your own address, for test sends. | "you@yourbusiness.com" |
delay_seconds | Pause between emails. Two seconds is gentle on your email provider. | 2 |
date_format | How dates from Excel are written. | "M/d/yyyy" |
email | How to send. See EMAIL-SETUP.md. |
In paths, write each \ twice, as shown, or use / instead.
Writing the message
Write it in Notepad like a normal email. Leave a blank line between paragraphs.
Hi {{First Name|there}},
Your {{Month}} statement for {{Company}} is attached. The balance due is **{{Balance Due}}**.
Pay online at https://example.com/pay or reply with any questions.
Thank you,
Dana
{{Column Name}}is replaced with that person's value. Capitals and spaces inside the braces don't matter, but the name must match a column heading.{{Column Name|fallback}}uses the fallback when the cell is blank.{{Today}}is today's date.**bold**makes text bold. Links starting withhttps://become clickable.- Values show exactly as they look in Excel, so format money and dates in Excel the way you want them to read.
- Outlook doesn't add your signature to emails sent by programs, so include your sign-off in the message.
If a field doesn't match any column, the check stops and tells you which one, before anything is sent.
Finding each person's attachment
A column of file names. Add a column like Attachment with each person's file name, such as Smith statement.pdf. Set "attachment_column": "Attachment".
A pattern. If your files are named after something in the list, like an account number (ACC-1001 October statement.pdf), set "attachment_pattern": "{{Account}}*.pdf". Each person gets the files in the attachments folder that match. If the field is blank for someone, they're held back rather than matching everyone's files.
You can use both: for example, everyone's own statement from the pattern plus a shared terms.pdf from the column.
Sending a new batch
Next month, change name (for example to "November statements"), update the list and files, and send. Using the same name would skip everyone who was sent the last batch under that name, if the subject and file names are the same.
Menu option 9 starts the current name over, keeping the old record under a new file name.
What's in the folder
| File | What it is |
|---|---|
Mail Merge.bat | Double-click to open the menu |
merge.json | Your settings |
message.txt | Your message |
sample-data/ | A sample list and statements. Delete when you're set up |
app/ | The program. You don't need to open these files |
previews/ | The check-and-preview pages from menu option 1 |
logs/ | <name> - results.csv lists every email sent, with the time and any error (menu option 8) |
secrets/ | Your encrypted SMTP password, if you use SMTP |
Good to know
- It's for emails to people you work with: statements, receipts, reminders, certificates, updates. It isn't a newsletter tool. It has no unsubscribe link or bounce handling, so don't use it to email marketing lists.
- Sending limits. Email providers limit how many emails an account can send a day. Personal Gmail accounts have a lower limit than Google Workspace or Microsoft 365. With Gmail or SMTP, reaching the limit stops the run with a clear message; run it again the next day and it carries on where it stopped. With Outlook, emails are handed to Outlook, so any that your provider refuses come back to your Outlook inbox as "undeliverable" messages.
- Large lists. At the default two seconds between emails, 300 emails take about ten minutes. Leave the window open until it finishes.
- Run it from another program (for advanced users):
powershell -ExecutionPolicy Bypass -File app\Run-Merge.ps1 -Preview. Use-Test -To you@example.com, or-Send -Yesto send. Exit code 0 = done, 2 = some emails had problems, 1 = failed.
Troubleshooting
| What you see | Fix |
|---|---|
| Double-clicking the .bat flashes and closes, or Windows warns about the file | Unblock the ZIP before extracting (step 1). If Windows SmartScreen shows "Windows protected your PC", click More info → Run anyway. |
| "The list has no "Email" column" | Set email_column to the exact heading of your email column. |
| "...uses {{Frist Name}}, but the list has no column with that name" | Fix the spelling in the subject or message so it matches a column heading. |
| "no file matches ..." | Check the attachments folder and the file names. In the preview, each held-back email shows the exact pattern it looked for. |
| "Classic Outlook for Windows was not found" | You're using the new Outlook app, which can't be automated by any program. Switch back to classic Outlook (see EMAIL-SETUP.md), or use SMTP. |
| Outlook asks "A program is trying to send an email" | Outlook shows this when it thinks your antivirus is out of date. Make sure Windows Security is on and up to date, or ask your IT person to allow programmatic access. |
| "The mail server refused the email" | Check the address and app password. Gmail needs an app password, not your normal password. |
| "daily sending limit was reached" | Wait until tomorrow and choose 3 again. Everyone already sent is skipped. |
| Someone says they didn't get it | Menu option 8 shows whether and when it was sent. Ask them to check spam. |
Need help?
Email evergreen.code.info@gmail.com. Describe what happened and include the message the program showed. See LICENSE.txt for your license terms.
Email setup
Mail Merge can send in two ways. Pick one and set "method" in merge.json.
| Classic Outlook | SMTP (Gmail and others) | |
|---|---|---|
| Best for | Offices on Microsoft 365 or Exchange | Gmail, Google Workspace, Zoho and similar |
| Passwords | None, uses your Outlook | An app password, stored encrypted |
| Needs | Classic Outlook installed on this PC | Nothing else |
| Sent emails appear in | Your Outlook Sent Items | Your provider's Sent folder (Gmail does this) |
Classic Outlook
- In
merge.json, set"method": "outlook". - That's it. Emails are sent from your default Outlook account and appear in Sent Items. To send from another account set up in Outlook, add
"outlook_account": "that@address.com".
Classic or new Outlook? Look at the top-right corner of Outlook. If you see a New Outlook switch turned on, you're using the new app, which no program can send through. Turn the switch off to go back to classic Outlook. If there's no switch and the window has File, Home and Send / Receive tabs, it's classic.
Save to Drafts first, if you like. Menu option 4 puts every email in your Outlook Drafts folder instead of sending. Open a few to check them, then send them from Outlook. Drafts are recorded too, so running option 4 or 3 again won't create them twice.
Keep Outlook open while sending. If Outlook is closed, Mail Merge opens it in the background and waits up to five minutes for the Outbox to empty before finishing. For large batches it's simplest to have Outlook open. If an email ever sits in your Outbox, opening Outlook sends it.
Gmail or Google Workspace
Gmail needs an app password, a separate 16-letter password just for this program. Your normal Gmail password won't work.
- Turn on 2-Step Verification for the Google account, if it isn't already: myaccount.google.com → Security.
- Create the app password: go to myaccount.google.com/apppasswords, type a name like "Mail Merge", and click Create. Copy the 16-letter password. If the page says app passwords aren't available, your Google Workspace administrator has turned them off. Ask them, or use classic Outlook.
- In
merge.json, set: ``json "email": { "method": "smtp", "from": "you@gmail.com", "from_name": "Your Business Name", "smtp_server": "smtp.gmail.com", "smtp_port": 587 } - In the menu, choose 5 and paste the app password. Nothing shows as you paste; that's normal. Press Enter.
The password is encrypted with Windows' built-in protection, so only your Windows account on this PC can use it. If you change PCs or Windows accounts, save it again.
Other email services
Any service that offers SMTP on port 587 with an app password works. Look for "SMTP settings" in your provider's help. For example, Zoho Mail uses smtp.zoho.com, port 587, with an app-specific password.
Port 465 isn't supported. Nearly every provider that offers 465 also offers 587.
Microsoft 365 and Outlook.com accounts
Microsoft is switching off password-based SMTP sending for Microsoft 365 business accounts. It is due to be off by default from the end of December 2026, and Outlook.com personal accounts already require Microsoft's newer sign-in. For these accounts, use classic Outlook.
Changelog
1.0.0 (2026-10-19)
First release.
- Lists: Excel (.xlsx) or CSV, with cell formatting kept (money, dates, percentages). No Excel needed.
- Personalized subject and message with
{{Column}}fields, fallbacks for blank cells, and{{Today}}. - Attachments per person from a column of file names, a pattern such as
{{Account}}*.pdf, or both. Several per email. - Checks before sending: invalid addresses, missing or oversized attachments, unknown fields, duplicates. Problem emails are held back and explained.
- Preview of every email in the browser, test sends to yourself, a Send column to choose who gets it, Cc and Bcc.
- Sends through classic Outlook (or saves to Drafts) or SMTP (STARTTLS on port 587) with an app password encrypted for the Windows user.
- Never twice: every email is recorded as it's sent; rerunning skips them. Stops after three failures in a row.
- A double-click menu for everything.