IronWord Mail Merge: Generate Personalized Documents from .NET, Without Microsoft Word
For years, .NET teams that needed to generate personalized Word documents at scale had two real options, and neither was good.
The first was to automate Microsoft Word itself through COM interop. It worked, until it didn't. Word interop is slow, fragile, requires a Word license on every machine, and falls apart the moment you try to run it inside a Linux container, an Azure Function, or any server environment where Word isn't installed. Most teams that tried it ended up rewriting around it.
The second was a custom find-and-replace pipeline. Read the .docx as XML or as text, swap placeholder tokens for real values, and save the result. That worked for simple cases and broke on real Word documents, which use complex field structures that look nothing like the simple {{FirstName}} tokens developers expect to find.
IronWord now supports mail merge natively. Templates designed in Microsoft Word, using the same merge field syntax Word itself produces, are populated directly from .NET code with no Word installation required.
What the feature does
The mail merge engine handles both styles of merge field that Word produces:
- Simple fields, in the «FieldName» format, which is what you get from typing <
> directly into the template - Complex fields, the multi-element XML structure Word generates through Insert → Quick Parts → Field
Templates can include single-value placeholders that get replaced once per document, and repeating regions bounded by «TableStart:Name» and «TableEnd:Name» markers, which expand to one row per data record.
That second piece is where the feature earns its place. Repeating regions are how a single template row in an invoice expands into a 50-row line-item table, or how one contact slot in a label sheet becomes a full Avery grid populated from a database.
Personalized cover letters from a CRM
Most teams hit mail merge first through sales operations. The CRM holds customer records; marketing or sales operations needs to send personalized cover letters, welcome packets, or renewal notices. The pattern is straightforward: one template, one row per customer, one personalized document out.
// Template: "Dear «FirstName», thank you for your order of «Amount»..."
DataTable customers = LoadFromSalesforce(); // FirstName, LastName, Amount, ...
foreach (DataRow row in customers.Rows)
{
var doc = new WordDocument("cover-letter-template.docx");
doc.MailMerge.Execute(row);
doc.SaveAs($"letters/{row["LastName"]}.docx");
}
// Template: "Dear «FirstName», thank you for your order of «Amount»..."
DataTable customers = LoadFromSalesforce(); // FirstName, LastName, Amount, ...
foreach (DataRow row in customers.Rows)
{
var doc = new WordDocument("cover-letter-template.docx");
doc.MailMerge.Execute(row);
doc.SaveAs($"letters/{row["LastName"]}.docx");
}
Imports System.Data
' Template: "Dear «FirstName», thank you for your order of «Amount»..."
Dim customers As DataTable = LoadFromSalesforce() ' FirstName, LastName, Amount, ...
For Each row As DataRow In customers.Rows
Dim doc As New WordDocument("cover-letter-template.docx")
doc.MailMerge.Execute(row)
doc.SaveAs($"letters/{row("LastName")}.docx")
Next
The data source is a DataTable, which means anything that loads into one (Salesforce, HubSpot, SQL Server, a CSV, an API response) becomes a mail merge source with no additional adapter code.
Invoices with repeating line items
Invoicing is where the repeating-region capability earns its keep. An invoice template has a header (customer name, order ID, dates), one row in a line-item table, and a footer (totals). When the merge runs, the single template row expands to one row per product, regardless of whether the order has one line item or fifty.
// Template:
// Bill To: «CustomerName» Order: «OrderId»
// ┌──────────┬─────┬──────────┐
// │ «TableStart:Items»«Product» │ «Qty» │ «LineTotal» «TableEnd:Items» │
// └──────────┴─────┴──────────┘
// Total: «Total»
var doc = new WordDocument("invoice-template.docx");
doc.MailMerge.ExecuteWithRegions(orderLineItems); // one row per product
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CustomerName", "Acme Industries" },
{ "OrderId", "10444" },
{ "Total", "$1,117.71" },
});
doc.SaveAs("invoice-10444.docx");
// Template:
// Bill To: «CustomerName» Order: «OrderId»
// ┌──────────┬─────┬──────────┐
// │ «TableStart:Items»«Product» │ «Qty» │ «LineTotal» «TableEnd:Items» │
// └──────────┴─────┴──────────┘
// Total: «Total»
var doc = new WordDocument("invoice-template.docx");
doc.MailMerge.ExecuteWithRegions(orderLineItems); // one row per product
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CustomerName", "Acme Industries" },
{ "OrderId", "10444" },
{ "Total", "$1,117.71" },
});
doc.SaveAs("invoice-10444.docx");
Imports System.Collections.Generic
' Template:
' Bill To: «CustomerName» Order: «OrderId»
' ┌──────────┬─────┬──────────┐
' │ «TableStart:Items»«Product» │ «Qty» │ «LineTotal» «TableEnd:Items» │
' └──────────┴─────┴──────────┘
' Total: «Total»
Dim doc As New WordDocument("invoice-template.docx")
doc.MailMerge.ExecuteWithRegions(orderLineItems) ' one row per product
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
{"CustomerName", "Acme Industries"},
{"OrderId", "10444"},
{"Total", "$1,117.71"}
})
doc.SaveAs("invoice-10444.docx")
The same pattern works for quotes, purchase orders, statements, and any document where a variable-length list sits inside a fixed-shape page.
Avery label sheets and envelopes
Shipping and operations teams generate labels and envelopes from contact lists, and the layouts (Avery 5160, 5161, 5163, and their international equivalents) are standardized templates that have existed for decades. IronWord populates them directly: each cell in the label grid picks up the appropriate contact from a single API call.
// Template: a 3×10 Avery 5160 grid, each cell has «CONTACT_FULLNAME» / «CONTACT_ADDRESS»
var doc = new WordDocument("avery-5160.docx");
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CONTACT_FULLNAME", "Jane Doe" },
{ "CONTACT_ADDRESS", "100 Main Street, Boston, MA 02108" },
});
doc.SaveAs("labels-jane.docx");
// Template: a 3×10 Avery 5160 grid, each cell has «CONTACT_FULLNAME» / «CONTACT_ADDRESS»
var doc = new WordDocument("avery-5160.docx");
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CONTACT_FULLNAME", "Jane Doe" },
{ "CONTACT_ADDRESS", "100 Main Street, Boston, MA 02108" },
});
doc.SaveAs("labels-jane.docx");
Imports System.Collections.Generic
' Template: a 3×10 Avery 5160 grid, each cell has «CONTACT_FULLNAME» / «CONTACT_ADDRESS»
Dim doc As New WordDocument("avery-5160.docx")
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
{"CONTACT_FULLNAME", "Jane Doe"},
{"CONTACT_ADDRESS", "100 Main Street, Boston, MA 02108"}
})
doc.SaveAs("labels-jane.docx")
For variable-length contact lists, the ExecuteWithRegions pattern from the invoice example applies: define one cell as the repeating unit, and the rest of the sheet fills automatically.
Multi-region reports
The most powerful application is multi-region reports. A quarterly summary might include "Top Customers" and "Top Products" sections, each driven by its own data table. With a DataSet containing multiple tables, the merge expands both regions in a single call.
// Template:
// Top Customers
// «TableStart:Customers» • «Name» | «City» | «Revenue» «TableEnd:Customers»
// Top Products
// «TableStart:Products» «ProductName»: «Revenue» «TableEnd:Products»
var ds = new DataSet();
ds.Tables.Add(topCustomers); // TableName = "Customers"
ds.Tables.Add(topProducts); // TableName = "Products"
var doc = new WordDocument("quarterly-report.docx");
doc.MailMerge.ExecuteWithRegions(ds); // expands both regions
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CompanyName", "Iron Software" },
{ "ReportDate", "Q2 2026" },
});
doc.SaveAs("Q2-2026-report.docx");
// Template:
// Top Customers
// «TableStart:Customers» • «Name» | «City» | «Revenue» «TableEnd:Customers»
// Top Products
// «TableStart:Products» «ProductName»: «Revenue» «TableEnd:Products»
var ds = new DataSet();
ds.Tables.Add(topCustomers); // TableName = "Customers"
ds.Tables.Add(topProducts); // TableName = "Products"
var doc = new WordDocument("quarterly-report.docx");
doc.MailMerge.ExecuteWithRegions(ds); // expands both regions
doc.MailMerge.Execute(new Dictionary<string, string>
{
{ "CompanyName", "Iron Software" },
{ "ReportDate", "Q2 2026" },
});
doc.SaveAs("Q2-2026-report.docx");
Imports System.Data
Imports System.Collections.Generic
' Template:
' Top Customers
' «TableStart:Customers» • «Name» | «City» | «Revenue» «TableEnd:Customers»
' Top Products
' «TableStart:Products» «ProductName»: «Revenue» «TableEnd:Products»
Dim ds As New DataSet()
ds.Tables.Add(topCustomers) ' TableName = "Customers"
ds.Tables.Add(topProducts) ' TableName = "Products"
Dim doc As New WordDocument("quarterly-report.docx")
doc.MailMerge.ExecuteWithRegions(ds) ' expands both regions
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
{"CompanyName", "Iron Software"},
{"ReportDate", "Q2 2026"}
})
doc.SaveAs("Q2-2026-report.docx")
Each DataTable in the DataSet matches its corresponding «TableStart:...» region by name, and the scalar fields fill the header values in the same pass.
For teams with document workflows like these waiting on a clean .NET implementation, the Iron Suite bundles IronWord alongside IronPDF, IronOCR, IronXL, and IronBarcode in a single license.
Start a free trial, no credit card required.
Why this works
A few things distinguish this approach from the alternatives:
Native Word syntax. Templates are designed in Microsoft Word, using the merge field tools Word already provides. Save as .docx, hand the file to IronWord, and the merge runs. No proprietary template language, no translation layer, no separate learning curve for the team designing the documents.
Both simple and complex field formats. Word produces two distinct field structures depending on how the field was inserted. IronWord handles both, which means existing templates designed by different team members in different ways all work without modification.
No external dependencies. The library runs entirely on .NET. No Word installation, no COM, no Office license, no interop fragility. The same code that runs on a Windows developer machine runs identically in a Linux container, an Azure Function, or a serverless build agent.
Summary
Mail merge has been a long-standing gap in the .NET document tooling space, and the workarounds have not aged well. IronWord's native mail merge closes that gap with a model that mirrors Word's own merge field syntax, supports both simple values and repeating regions, and runs without any of the deployment baggage that has historically come with document automation in .NET.
For teams generating invoices, labels, letters, contracts, and reports at scale, this is the cleanest path from a single template to thousands of personalized documents.
Frequently Asked Questions
What is IronWord Mail Merge?
IronWord Mail Merge is a feature that allows you to generate personalized Word documents from .NET applications without requiring a Microsoft Word installation.
Can IronWord Mail Merge generate documents at scale?
Yes, IronWord Mail Merge is designed to handle the generation of personalized Word documents at scale, making it ideal for large batch processing.
Is Microsoft Word required to use IronWord Mail Merge?
No, IronWord Mail Merge does not require Microsoft Word to be installed, allowing for a seamless document generation experience within .NET applications.
What types of documents can be generated using IronWord Mail Merge?
IronWord Mail Merge can generate a variety of documents including invoices, letters, labels, and reports using Word templates.
How does IronWord Mail Merge integrate with .NET?
IronWord Mail Merge is natively supported in .NET, providing developers with a straightforward and efficient way to implement mail merge functionality in their applications.
What are the benefits of using IronWord Mail Merge over traditional methods?
Using IronWord Mail Merge eliminates the need for Microsoft Word, reduces dependency on third-party software, and provides a scalable solution for generating documents from .NET applications.
Can IronWord Mail Merge be used for generating reports?
Yes, IronWord Mail Merge can be used to generate detailed reports from Word templates, making it versatile for various documentation needs.
Is IronWord Mail Merge suitable for automated document generation?
Yes, IronWord Mail Merge is ideal for automated document generation, allowing developers to create personalized documents programmatically within their .NET applications.




