Welkomstbrieven voor veertig nieuwe klanten, een certificaat voor elke deelnemer, een factuur per bestelling: hetzelfde document, elke keer een ander record. Met GroupDocs.Assembly.Mcp die lokaal draait (dnx GroupDocs.Assembly.Mcp --yes), doet Claude Desktop, Cursor of Copilot het met één tool‑aanroep, assemble_per_record, die uw Word‑sjabloon één keer per element van een JSON‑array invult, elk resultaat opslaat als een eigen PDF met een door u gekozen veld als naam, en de records rapporteert die mislukt zijn in plaats van te stoppen bij de eerste:

Maak een welkomstbrief‑PDF voor elke klant in customers.json met welcome-letter.docx, en noem elk bestand naar de klant.

De stapsgewijze versie met configuratie en probleemoplossing staat in de documentatie: How to generate one document per record (mail merge) with an AI agent.

Waarom de agent niet laten loopen?

Het kan, en voor vijf records is dat prima. Maar een lus van veertig tool‑aanroepen is veertig kansen voor de agent om de tel kwijt te raken, een record over te slaan wanneer de context lang wordt, of “behulpzaam” één brief te herformuleren. Een per‑record tool verplaatst de lus naar de server: één aanroep, één sjabloon, één doorloop van de array, en een rapport dat precies aangeeft hoeveel bestanden er zijn geschreven en welke records het niet hebben gehaald. De taak van de agent is het sjabloon, het gegevensbestand en het naamgevingsveld te kiezen, en vervolgens het rapport aan u terug te lezen.

Wat controleert de agent eerst?

Twee dingen: hoe de gegevens eruitzien, en wat het sjabloon leest. In onze test, vijf klanten in customers.json en een eenpagina‑welkomstbrief:

-> get_document_info {"file":{"filePath":"customers.json"}}
<- { "dataKind": "json",
     "data": { "root": "array", "length": 5, "keys": null } }

-> get_template_fields {"template":{"filePath":"welcome-letter.docx"}}
<- { "dataSourceNames": [ "customer" ],
     "fields": [ "customer.Contact", "customer.Plan", "customer.Name",
                 "customer.Manager", "customer.FirstInvoice" ] }

Het sjabloon noemt één record customer; de server werkt dat zelf uit, dus de agent hoeft dataSourceName niet door te geven.

Eén aanroep, vijf records, één fout

-> assemble_per_record {"template":{"filePath":"welcome-letter.docx"},
     "data":{"filePath":"customers.json"},"fileNameField":"Name",
     "format":"pdf"}
<- Assembled 4 of 5 document(s) from 'welcome-letter.docx' with
   'customers.json', one per record (each record as 'customer'), as PDF.
   Saved to:
   .../Contoso Pharma.pdf
   .../Fabrikam Logistics.pdf
   .../Tailspin Toys.pdf
   .../Litware Labs.pdf
   1 record(s) failed:
   record 4: InvalidOperationException: ... Can not get the value of
   member 'FirstInvoice' on type 'System.Data.DataRow'. The template
   reads 'FirstInvoice', which the data does not have. ...

Record 4, Wingtip Travel, had geen eerste‑factuurdatum in de gegevens. De andere vier brieven werden geschreven, elk genoemd naar de klant, en de fout kwam terug met het recordnummer en het ontbrekende veld. Een agent rapporteert dat als “vier brieven klaar; Wingtip Travel heeft geen eerste‑factuurdatum, wilt u dat ik de regel leeg laat of wacht op de datum?”. Dat is het gesprek dat u wilt, in plaats van veertig bestanden en geen idee welke fout is.

Hoe de fan‑out werkt

customers.json  [ {Name:"Contoso Pharma",...}, {Name:"Fabrikam ..."},
                  ... 5 records ]
        |
assemble_per_record {"template":{"filePath":"welcome-letter.docx"},
                     "data":{"filePath":"customers.json"},
                     "fileNameField":"Name","format":"pdf"}
        |
        +--> Contoso Pharma.pdf
        +--> Fabrikam Logistics.pdf     ... 2 more
        '--> "4 of 5 ... 1 record(s) failed: record 4 ..."

Drie parameters doen het werk. fileNameField benoemt elke output op basis van een record‑veld, customer.Name of een factuurnummer; zonder dit krijgen bestanden een opeenvolgend nummer. recordsPath wijst naar de array wanneer de JSON een object is dat er één bevat, zoals "customers" of "batch.items". En maxDocuments is de rem.

Wat stopt een uit de hand gelopen batch?

maxDocuments, dat standaard 100 is en kan oplopen tot 1000. Voordat er iets wordt weggeschreven, telt de server de records; als er meer zijn dan de limiet, weigert hij. We zetten het op 3 voor onze vijf klanten:

-> assemble_per_record {..., "maxDocuments":3}
<- Per-record assembly failed for 'welcome-letter.docx': there are 5
   records, more than maxDocuments=3. Raise 'maxDocuments' (up to 1000)
   or pass fewer records.

Niets werd weggeschreven. Voor een mailing die naar echte mensen gaat, is dat de juiste standaard: een prompt die per ongeluk naar de volledige CRM‑export wijst, krijgt een vraag terug, niet tienduizend PDF’s.

Mijn gegevens zijn een spreadsheet, geen JSON

Per‑record generatie leest alleen JSON: een array van objecten, of een object dat er één bevat. Wanneer de lijst zich in een CSV of een werkblad bevindt, zet de agent de rijen eerst om naar JSON, wat agenten goed kunnen, en schrijft het bestand naar de opslagmap. Als u één document wilt dat elke rij opsomt, een prijslijst of een register, dan is dat assemble_document met een <<foreach>> in het sjabloon, en die leest CSV‑ en spreadsheet‑bestanden direct.

Eerlijke beperkingen

  • Per‑record assemblage leest alleen JSON; CSV‑ en spreadsheet‑gegevens moeten eerst worden geconverteerd.
  • Evaluatiemodus: elk gegenereerd bestand begint met “Evaluation Only. Created with GroupDocs.Assembly 26.7…”, en e‑mail‑sjablonen behouden alleen hun platte‑tekst‑body. Licentieer de server vóór een echte mailing.
  • Een ontbrekend veld laat dat record falen. allowMissingMembers: true schrijft het met een lege regel; bepaal wat u wilt.
  • Datums: schrijf ze als 2026-12-01T00:00:00 en formatteer ze in het sjabloon, zoals onze brief doet met <<[customer.FirstInvoice]:"d MMMM yyyy">>. Een datum‑enkel‑string die met één dag verschoven is op een UTC+2‑machine in onze test.
  • De server schrijft bestanden; hij verzendt ze niet. Levering is een aparte stap.

Veelgestelde vragen

Kan ik mail‑merge doen met Claude zonder de klantenlijst te uploaden?
Ja. De server draait lokaal en leest customers.json uit uw opslagmap; de gegenereerde PDF‑s worden daar ook weggeschreven.

Hoe worden de output‑bestanden genoemd?
Op basis van het record‑veld dat u opgeeft als fileNameField, hier de klantnaam, dus Contoso Pharma.pdf. Zonder dit krijgt het een opeenvolgend nummer.

Kan elk document een e‑mail zijn in plaats van een PDF?
Ja: gebruik een EML‑ of MSG‑sjabloon, of geef format: "eml" door. In evaluatiemodus wordt de HTML‑body van een e‑mail vervangen door de evaluatielijn, dus test e‑mail‑output met een licentie.

Ga dieper