-
Notifications
You must be signed in to change notification settings - Fork 0
Document Mail Merge
The #mail-merge(...) engine is designed to generate individual multi-page or single-page documents per recipient record (e.g. letters, invoices, certificates, and reports).
#mail-merge(
data,
template,
filter: none,
sort-by: none,
reverse: false,
start: 1,
limit: none,
pagebreak: true,
reset-page-counter: false,
trim: true,
default-value: "",
on-empty: [No matching records found.]
)By default (pagebreak: true), #mail-merge inserts a #pagebreak() after rendering each record except the last one.
#mail-merge(
data,
pagebreak: true,
record => [ ... ]
)When printing multi-page documents (like 2-page invoices or contracts per client), you often want each recipient's document to start at page 1.
Setting reset-page-counter: true executes counter(page).update(1) at the start of each record iteration:
#set page(
paper: "us-letter",
footer: context [
#align(center)[Page #counter(page).display() of #counter(page).final().at(0)]
]
)
#mail-merge(
data,
reset-page-counter: true,
record => [
// Page 1 of recipient document
...
#pagebreak()
// Page 2 of recipient document
...
]
)Inside your template closure record => [ ... ], every record dictionary is automatically enriched with internal helper metadata:
| Field Key | Type | Description | Helper Function |
|---|---|---|---|
_index |
int |
1-based index of current record | record-index(record) |
_zero_index |
int |
0-based index of current record | N/A |
_total |
int |
Total count of filtered records | record-total(record) |
_is_first |
bool |
True if this is the first record | is-first-record(record) |
_is_last |
bool |
True if this is the last record | is-last-record(record) |
#import "@preview/mailmerge:0.1.0": mail-merge, record-index, record-total
#mail-merge(
data,
record => [
#align(right)[Document #record-index(record) of #record-total(record)]
...
]
)If filtering returns zero matching records, #mail-merge renders the on-empty content parameter instead of crashing or generating an empty PDF.
#mail-merge(
data,
filter: record => record.Status == "VIP",
on-empty: [
#align(center)[
*No VIP records found in the current dataset.*
]
],
record => [ ... ]
)