Mail Merge from CSV

Paste a template with {{column}} placeholders and a CSV — get one filled-in document per row. Runs entirely in your browser, no upload, no sign-up.

Try:
Merged output

About mail merge

This tool does a classic mail merge: it fills a text or markdown template once for every row of a CSV, then joins the results. Each {{Column}} placeholder in the template is replaced by that row's value for the matching CSV header column, so one small template plus a spreadsheet of data becomes a batch of personalized letters, emails, labels, reminders, or messages.

Everything runs locally in your browser. Your template and data are never uploaded to a server, and it works offline.

How it works

  1. Template — write your text and drop {{name}} style placeholders where the per-row values should go. The name inside the braces must match a column header in your CSV.
  2. CSV data — paste rows where the first line is the header (the column names) and each following line is one record.
  3. Pick the placeholder style, the CSV delimiter, and a separator to put between the generated documents, then run.

Worked example

Template:

Hi {{name}},

Your invoice for ${{amount}} is due on {{due}}.

Thanks!

CSV:

name,amount,due
Alice,120,2026-08-01
Bob,90,2026-08-05

Output (with the default --- divider between rows):

Hi Alice,

Your invoice for $120 is due on 2026-08-01.

Thanks!

---

Hi Bob,

Your invoice for $90 is due on 2026-08-05.

Thanks!

Options

Limits and edge cases

FAQ

What placeholder syntax should I use?

By default, wrap a column name in double curly braces: {{name}}. If your text already contains literal braces, switch the placeholder style to {column} or <<column>> so your merge fields don't clash with them.

Does the first CSV row have to be a header?

Yes. The first line is read as the column names, and every line after it is a data row that produces one output. Placeholders are matched to those header names.

What happens if a placeholder's column isn't in the CSV?

It depends on the If a placeholder column is missing setting: replace with nothing drops it, keep the placeholder text leaves {{that}} in place so you can spot the mismatch, and show an error stops the merge and names the missing column. Note a column that exists but is blank for a row always renders empty.

My data uses semicolons or tabs — is that supported?

Yes. Set the CSV delimiter to semicolon (common in European spreadsheet exports) or tab (for TSV). Quoted fields containing the delimiter or newlines are handled correctly.

Can I do loops or if/else in the template?

No — mail merge is plain {{column}} substitution repeated per row. For Handlebars/Mustache logic ({{#each}}, {{#if}}, nested paths) against a single JSON object, use the render-template tool.

Is my data uploaded anywhere?

No. The merge runs entirely in your browser using WebAssembly. Your template and CSV never leave your device, and the page works offline once loaded.

Developer & Automation Access

Run it from the terminal

Same engine as this page, headless — via the gizza CLI:

gizza tool mail-merge 'Hi {{name}},

Your invoice for ${{amount}} is due on {{due}}.

Thanks!' 'csv=name,amount,due
Alice,120,2026-08-01
Bob,90,2026-08-05'

New to the CLI? Get gizza →

Open it by URL

Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:

https://gizza.ai/tools/mail-merge/?template=Hi%20%7B%7Bname%7D%7D%2C%0A%0AYour%20invoice%20for%20%24%7B%7Bamount%7D%7D%20is%20due%20on%20%7B%7Bdue%7D%7D.%0A%0AThanks%21&csv=name%2Camount%2Cdue%0AAlice%2C120%2C2026-08-01%0ABob%2C90%2C2026-08-05&syntax=double_curly&delimiter=comma&on_missing=empty&case_insensitive=true&separator=divider

Machine-readable descriptor: tool.json — title + parameters JSON Schema for agents.