Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Geavanceerde mail merge met Apps Script en Docs

Gebruik Apps Script om gepersonaliseerde documenten te genereren vanuit een Google Sheets-dataset en een Google Docs-sjabloon, inclusief PDF-export en automatisch verzenden.

Wat is mail merge met Apps Script?

Mail merge combineert een sjabloondocument met een datalijst om gepersonaliseerde documenten te genereren. Met Google Apps Script automatiseer je dit proces volledig: het script leest de data uit Sheets, maakt voor elke rij een kopie van het Docs-sjabloon, vervangt de placeholders door echte data en exporteert het resultaat als PDF.

Veelvoorkomende toepassingen:

  • Gepersonaliseerde contracten voor klanten
  • Certificaten voor deelnemers aan een training
  • Facturen of offertes op basis van spreadsheet-data
  • Individuele rapportages per medewerker
info

Verder dan een kant-en-klare add-on

Dit voorbeeld gaat verder dan eenvoudige mail merge add-ons. Met Apps Script heb je volledige controle: tabellen toevoegen, conditionele inhoud, automatisch e-mailen en archiveren in Drive.

Stap 1: Het Docs-sjabloon aanmaken

Maak een Google Docs-document met placeholders voor variabele inhoud. Gebruik dubbele accolades als scheidingsteken:

{{NAAM}}
{{BEDRIJF}}
{{DATUM}}
{{BEDRAG}}

Noteer de document-ID uit de URL. Dat is het lange deel met letters en cijfers na /d/.

Stap 2: De Sheets-datalijst aanmaken

Maak een Google Sheets-bestand met de data. De eerste rij bevat kolomhoofden die exact overeenkomen met de placeholders in het sjabloon (zonder de accolades):

NAAM BEDRIJF DATUM BEDRAG EMAIL STATUS
Jan Jansen Bedrijf A 01-06-2026 1250,00 jan@bedrijfa.nl
Eva de Vries Bedrijf B 01-06-2026 2400,00 eva@bedrijfb.nl

De kolom STATUS wordt later door het script bijgewerkt, zodat een rij niet dubbel wordt verwerkt.

Stap 3: Het Apps Script schrijven

var SJABLOON_ID = 'jouw-docs-sjabloon-id-hier';
var SHEETS_ID = 'jouw-sheets-id-hier';
var EXPORT_MAP_NAAM = 'Gegenereerde Documenten';

function voerMailMergeUit() {
  var ss = SpreadsheetApp.openById(SHEETS_ID);
  var sheet = ss.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var headers = data[0];
  var statusIndex = headers.indexOf('STATUS');
  var naamIndex = headers.indexOf('NAAM');
  var emailIndex = headers.indexOf('EMAIL');

  var exportMap = getOfMaakMap(EXPORT_MAP_NAAM);

  for (var i = 1; i < data.length; i++) {
    var rij = data[i];

    if (rij[statusIndex] === 'Verzonden') {
      continue;
    }

    var plaatsvervangingen = {};
    headers.forEach(function(header, index) {
      plaatsvervangingen['{{' + header + '}}'] = rij[index] || '';
    });

    var pdfBlob = genereerDocument(plaatsvervangingen, rij[naamIndex]);
    var bestand = exportMap.createFile(pdfBlob);

    if (rij[emailIndex]) {
      verstuurEmail(rij[emailIndex], rij[naamIndex], bestand.getUrl());
    }

    sheet.getRange(i + 1, statusIndex + 1).setValue('Verzonden');

    Utilities.sleep(500);
  }

  console.log('Mail merge voltooid.');
}

function genereerDocument(plaatsvervangingen, naam) {
  var sjabloon = DriveApp.getFileById(SJABLOON_ID);
  var kopie = sjabloon.makeCopy('Temp - ' + naam);
  var doc = DocumentApp.openById(kopie.getId());
  var body = doc.getBody();

  Object.keys(plaatsvervangingen).forEach(function(placeholder) {
    body.replaceText(placeholder, String(plaatsvervangingen[placeholder]));
  });

  doc.saveAndClose();

  var pdfBlob = kopie.getAs('application/pdf');
  pdfBlob.setName(naam + ' - Document.pdf');

  kopie.setTrashed(true);

  return pdfBlob;
}

function getOfMaakMap(mapNaam) {
  var mappen = DriveApp.getFoldersByName(mapNaam);
  if (mappen.hasNext()) {
    return mappen.next();
  }
  return DriveApp.createFolder(mapNaam);
}

function verstuurEmail(emailAdres, naam, driveUrl) {
  var onderwerp = 'Uw document is beschikbaar';
  var bericht = 'Beste ' + naam + ',

' +
    'Uw document is gegenereerd en beschikbaar via de volgende link:
' +
    driveUrl + '

' +
    'Met vriendelijke groet,
' +
    'Cloud Captains';

  GmailApp.sendEmail(emailAdres, onderwerp, bericht);
}

Let op de regel body.replaceText(placeholder, ...): het eerste argument van replaceText wordt door Apps Script als reguliere expressie behandeld. Accolades zijn daarin veilig, maar gebruik je tekens als (, ), [, . of * in een placeholder, escape die dan.

Conditionele inhoud toevoegen

Soms moet een sectie alleen verschijnen voor bepaalde rijen, bijvoorbeeld een premium-blok. Markeer de sectie in het sjabloon met een begin- en eind-placeholder en verwijder die naargelang de data:

function verwerkConditioneleInhoud(body, data) {
  if (data['{{PREMIUM}}'] === 'Ja') {
    body.replaceText('\{\{PREMIUM_SECTIE_BEGIN\}\}', '');
    body.replaceText('\{\{PREMIUM_SECTIE_EIND\}\}', '');
  } else {
    body.replaceText(
      '\{\{PREMIUM_SECTIE_BEGIN\}\}[\s\S]*?\{\{PREMIUM_SECTIE_EIND\}\}',
      ''
    );
  }
}

In de Ja-tak verwijder je alleen de twee markeringen, zodat de inhoud blijft staan. In de else-tak verwijder je de markeringen plus alles ertussen in een keer met een reguliere expressie.

warning

Test eerst op een paar rijen

Test het script altijd eerst op een kleine subset van de data, bijvoorbeeld alleen rij 2, voordat je het op de volledige lijst loslaat. Voeg tijdelijk een break-statement onderaan de loop toe om na de eerste rij te stoppen, en verwijder dat weer als alles klopt.

Foutafhandeling toevoegen

Vang fouten af en stuur jezelf een melding, zodat een mislukte run niet onopgemerkt blijft:

function voerMailMergeUitMetFoutafhandeling() {
  try {
    voerMailMergeUit();
  } catch (e) {
    GmailApp.sendEmail(
      Session.getActiveUser().getEmail(),
      'Mail merge fout',
      'Er is een fout opgetreden: ' + e.message + '

Stack: ' + e.stack
    );
    console.error('Mail merge fout: ' + e.message);
  }
}

Het script starten vanuit Sheets

Wil je het script met een knop in je spreadsheet starten in plaats van vanuit de editor? Voeg dan een eigen menu toe.

Een eigen menu toevoegen

  1. Open het Apps Script-project via Extensies en dan Apps Script.
  2. Voeg een onOpen()-functie toe die het menu opbouwt:
  3. ```javascript
  4. function onOpen() {
  5. SpreadsheetApp.getUi()
  6. .createMenu('Mail Merge')
  7. .addItem('Start', 'voerMailMergeUitMetFoutafhandeling')
  8. .addToUi();
  9. }
  10. ```
  11. Sla op en herlaad het spreadsheet. Het menu Mail Merge verschijnt in de menubalk.
  12. Klik op Start en doorloop de autorisatie de eerste keer.
lightbulb

Houd je IDs uit de code

Zet SJABLOON_ID en SHEETS_ID niet hardcoded in je script als je het deelt. Bewaar ze via PropertiesService.getScriptProperties() en lees ze in runtime uit. Zo lekken gevoelige verwijzingen niet mee in een gedeelde kopie.

Kan ik ook Word-documenten genereren?

Nee, Apps Script werkt native met Google Docs. De PDF-export werkt goed voor de meeste distributiedoeleinden. Wil je per se een bewerkbaar Word-bestand, exporteer dan via de Drive API met het MIME-type voor docx in plaats van pdf.

Is er een limiet aan het aantal documenten dat ik kan genereren?

Ja. Een script-uitvoering mag maximaal 6 minuten duren (geldt zowel voor gratis Gmail- als Workspace-accounts, situatie juni 2026). Afhankelijk van de complexiteit haal je daarmee grofweg 20 tot 100 documenten per run. Voor grotere batches verdeel je het werk over meerdere runs of een tijdtrigger.

Hoe zit het met de opmaak van getallen en datums?

Getallen en datums uit Sheets worden automatisch naar tekst omgezet, soms anders dan je verwacht. Formatteer een datum voor vervanging met Utilities.formatDate(new Date(datum), 'Europe/Amsterdam', 'dd-MM-yyyy') en gebruik voor bedragen bijvoorbeeld Utilities.formatString.

Waarom zie ik na een time-out maar de helft van mijn documenten?

Omdat het script per rij de kolom STATUS op Verzonden zet, worden al verwerkte rijen bij een volgende run overgeslagen. Start het script gewoon opnieuw; het pakt de resterende rijen op.

Hoe beveilig ik gevoelige data in het script?

Gebruik PropertiesService voor opslag van gevoelige configuratie zoals IDs of API-sleutels in plaats van ze in de code te zetten. Beperk de toegang tot het spreadsheet tot bevoegde personen en deel het sjabloon niet breder dan nodig.

Worden de tijdelijke kopieen netjes opgeruimd?

Ja. De functie genereerDocument zet elke tijdelijke kopie na de PDF-export in de prullenbak via setTrashed(true). Leeg af en toe je prullenbak in Drive om opslagruimte vrij te maken.