Sei es die Einladung zur Geburtstagsfeier, ein Rundschreiben an die Vereinsmitglieder oder eine andere Marketingaktivität. Serienbriefe ermöglichen eine automatisierte und trotzdem persönliche Ansprache der Empfänger.
In diesem Beitrag möchte ich zeigen, wie man mit Hilfe von Google Sheets und Google Docs auch aufwändig gestaltete Dokumente an eine Vielzahl an Empfängern schicken kann.
Für Lesefaule gibt es am Ende natürlich auch die Videovariante.
Ziel des Projektes
Dieser Beitrag baut auf einem älteren Beitrag von mir auf, in dem ich bereits automatisiert Dokumente erstellt habe. Damals habe ich die Platzhalter einzeln abgefragt. Das war aber recht unpraktikabel, daher möchte ich in diesem Projekt alle Platzhalter aus einem Google Spreadsheet beziehen.
Zunächst einmal die Ziele, die ich mir für diesen Hack gesetzt habe.
- Erstellen einer Serienbriefvorlage mit Platzhaltern (Format {{platzhalter}})
- Google Sheet Tabellendokument mit den Empfängern und den zu ersetzenden Platzhaltern
- Automatisches Erstellen und Ablegen der generierten Briefe als PDF in einem Ordner
Erstellen der Briefvorlage
Ich habe erst angefangen meinen Brief in Google Docs zu schreiben, wie ich es in dem vorhergehenden Beitrag gemacht habe.
Dann ist mir aber eine spannende Idee gekommen!
Google Slides bietet viel mehr Formatierungsoptionen und bietet somit eine clevere Alternative zum erstellen von Dokumenten an. Dort kann man mit Formen und Illustrationen arbeiten, und es ist hier ebenfalls möglich die Platzhalter austauschen zu lassen.
Ich werde als Dokument einfach beides unterstützen:
Erstellen eines Spreadsheets für die Platzhalter
Als letztes erstellen wir unser Spreadsheets in dem sich die einzelnen Empfänger befinden und die Werte für die Platzhalter.
Zwei der Felder werden eine Bedeutung in meiner Logic haben:
- template: Document-Id der Vorlage (so sind wir flexibel)
- filename: Dateiname der erzeugt werden soll (wenn leer, wird keine Datei angelegt)
App-Script Magic
Für alle Nicht-Coder unter euch ist dieser Abschnitt, bis auf den eigentlichen Script, weniger interessant. (Siehe auch GitLab Project)
BATCH_SIZE=50;
/*
Beim start werden neue Menüpunkte zu Sheets hinzugefügt
*/
function onOpen() {
renderMenu();
}
/*
Aktualisiert das Menü
*/
function renderMenu(){
var ui = SpreadsheetApp.getUi();
var progress=PropertiesService.getDocumentProperties().getProperty('progress');
if(progress>0){
ui.createMenu('Serienbrief')
.addItem('Neu starten', 'startOver')
.addItem('Bei Eintrag '+parseInt(progress)+' fortsetzen', 'createDocuments')
.addToUi();
}else{
ui.createMenu('Serienbrief')
.addItem('Erstellen', 'startOver')
.addToUi();
}
}
/*
Startet einen neuen Vorgang
*/
function startOver(){
PropertiesService.getDocumentProperties().setProperty('progress', 0);
createDocuments();
}
/*
Konvertiere die aktuelle Datarange in ein Object-Array und nutze die erste Zeile als Object-Properties
*/
function spreadSheetToObjects(){
//Alle gefüllten Spalten auslesen
var range=SpreadsheetApp.getActiveSheet().getDataRange().getDisplayValues();
//Array für alle Einträge
var data=[];
for(var row=1; row<range.length; row++){ //Durchläuft alle Zeilen bis auf die Überschriften (daher row=1)
var dataObject={};
for (var col=0; col<range[0].length; col++){ //Durchläuft alle Spalten
var key=range[0][col]; //Übernimmt die Überschrift als Object-Property
var value=range[row][col]; //Schreibt den Wert der aktuellen Zeile ins Objekt
dataObject[key]=value; // Fügt den Eintrag zum Array hinzu
}
data.push(dataObject); //Fügt das Datenobjekt in die Liste hinzu
}
return data; //Gibt das Object-Array zurück
}
/*
Zielordner für die erstellten Dokumente.
Wird als "gen" auf gleicher Ebene wie das Spreadsheet angelegt
*/
function getTargetFolder(){
var folderName="gen"; //Name des Zielordners
//Aktuellen Ordner ermitteln:
var folder=DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).getParents().next();
//Gibt es einen "gen" Ordner?
var genFolders=folder.getFoldersByName(folderName);
if(genFolders.hasNext()){
return genFolders.next();
}else{
//"gen" Ordner erstellen
return folder.createFolder(folderName);
}
}
function createDocument(dataObject, targetFolder){
//Template-File laden
var templateFile=DriveApp.getFileById(dataObject.template);
//Dann eine Kopie im "gen" Ordner erstellen
var document=templateFile.makeCopy(targetFolder);
//Schauen, ob es sich um eine Präsentation handelt:
if(document.getMimeType()=="application/vnd.google-apps.presentation"){
//Dann das Dokument aus der Präsentation erstellen
var slideApp=SlidesApp.openById(document.getId());
//Alle Slides durchlaufen
slideApp.getSlides().forEach(slide=>{
//Alle Platzhalter ersetzen
for(var key in dataObject){
slide.replaceAllText('{{'+key+'}}',dataObject[key]);
}
});
slideApp.saveAndClose();
}else if(document.getMimeType()=="application/vnd.google-apps.document"){
//Dokument ist ein Textdokument -> Offnen und Platzhalter tauschen
var docApp=DocumentApp.openById(document.getId());
for(var key in dataObject){
docApp.getBody().replaceText('{{'+key+'}}', dataObject[key]);
}
docApp.saveAndClose();
}else{
//unknown type - Hier abbrechen
return;
}
//Create pdf export
var pdfFile=getTargetFolder().createFile(
DriveApp.getFileById(document.getId()).getAs(MimeType.PDF),
).setName(dataObject.filename);
//Originaldokument löschen
document.setTrashed(true);
}
/*
Hauptfunktion, die alle Daten aus dem Spreadsheet ausliest und die Dokumente erzeugt
*/
function createDocuments(){
//Die Dokumente in Objekte umwandeln
var data=spreadSheetToObjects();
var targetFolder=getTargetFolder();
var start=parseInt(PropertiesService.getDocumentProperties().getProperty('progress'));
//Durchlaufe alle Objekte und lege die Dokumente an
for(var i=start; i<data.length && i< (start+BATCH_SIZE); i++){
createDocument(data[i], targetFolder);
}
if(i<data.length){
SpreadsheetApp.getUi().alert("Es sind zu viele Einträge für einen Durchlauf. Nutzen Sie die Funktion \"Fortsetzen\" im Menü um die nächsten Dokumente zu erstellen");
PropertiesService.getDocumentProperties().setProperty('progress', i);
renderMenu();
}
}
Das ganze in Action
Hier kommt, wie versprochen, noch das Endergebnis als Video:
Update
Nachdem einige Nutzer in das Problem mit dem Google Script-Timeout gelaufen sind, habe ich den Script so erweitert, dass er nach einer bestimmten Anzahl (BATCH_SIZE) von Dokumenten aufhört und dann an dieser Stelle fortsetzen kann.
Typische Fehlermeldungen:
Exception: Es konnte kein Element mit der angegebenen ID gefunden werden. Möglicherweise haben Sie das Element nicht bearbeitet oder Sie haben nicht die erforderliche Berechtigung, um darauf zuzugreifen. | Es wurde nicht die richtige Dokument-Id verwendet. Die ID muss von dem Text Dokument oder der Präsentation sein. Nicht vom Scripteditor. |
Exception: SpreadsheetApp.getUi() kann aus diesem Kontext nicht abgerufen werden. at onOpen(Code:5:27) | Es wurde der falsche Code-Editor verwendet. Der Script muss im Code-Editor von Google Sheets eingetragen werden. |
Hi!
super einfach anzuwenden, danke!
Gibt es eine Möglichkeit einen der Datenpunkte als ein QR Code auf dem GDoc darzustellen?
VG Jan
Witzige Idee! So etwas könnte man entwickeln. Sehe da keine Hürden. Mit dem vorhanden Code aber nicht möglich.
Naja, es gibt doch Barcode Fonts die einen Text als Barcode darstellen.
Gute Sache! Leider gibts bei mir diesen Fehler: „Exception: Unexpected error while getting the method or property getFileById on object DriveApp.“ Weisst du woran das liegen könnte?
Wenn du den Script das erste Mal ausführst, sollte eine Berechtigungsanfrage kommen. Der muss auf jeden Fall zugestimmt werden.
Ich konnte das Skript beim ersten Mal problemlos ausführen. Jetzt habe ich weitere Zeilen dazu genommen und bekomme dieselbe Fehlermeldung. Kann/Muss ich die Berechtigung nochmal neu erteilen?
Hallo Igmar,
bitte schau ob du in jeder Zeile die ID vom Template eingetragen hast und das jede Zeile ausgefüllt ist.
Grüße
Tobias
Danke für Deine Antwort. Ich habe den Fehler mittlerweile gefunden. Ich dachte ich könnte mein Sheet etwas pimpen so dass ich einfach die Liste nach und nach weiterführen kann. Also habe ich bei den bereits erstellten Serienbriefen das Template entfernt. Dein Skript erwartet offenbar ab Zeile 1 eine ID (Hatte ich nicht auf dem Zettel). Ist dort keine wird nicht weiter ausgeführt. Ist zwar schade dass mein Plan hier nicht aufgeht, aber auch nicht weiter wild. Das Skript ist dennoch super und hilft mir sehr weiter. Danke dafür!
Guter Punkz, könnte man mal anpassen
Hi Tobias, cooler Code und danke für das Tutorial! Eine Frage: wenn ich statt einem PDF am Ende Word (docx) files haben möchte, was genau muss dann angepasst werden? VG
Hi Tobias, coole Sache und danke für das Tutorial. Wenn ich am Ende anstatt eines PDF ein DOCX und oder Google Docs File raushaben möchte (um es noch nachträglich etwas bearbeiten zu können bevor ich es finalisiere) oder im Zweifel sogar beides PDF und DOCX, wie würde das funktionieren? VG
Hi Yukio,
danke für dein Feedback. Das müsste dann mit
DriveApp.getFileById(document.getId()).getAs(MimeType.MICROSOFT_WORD),
funktionieren, habe ich aber noch nicht getestet.Hi Tobias, leider funktioniert das bei mir nicht und ich bekomme einen Error. Sowohl GOOGLE_DOCS (ERROR: Exception: Blob-Objekt muss für diesen Vorgang eine Datenquelle, die nicht null sein darf, aufweisen.) als auch MICROSOFT_WORD (ERROR: Exception: Das Umwandeln von application/vnd.google-apps.document in application/vnd.openxmlformats-officedocument.wordprocessingml.document wird nicht unterstützt.) gehen nicht. Hast du eine Idee, was man sonst noch anpassen müsste? Würde mir super weiterhelfen, danke!
Hallo Yikio,
bist du dir sicher, dass du die richtige Document-Id eingetragen hast. Das die Datenquelle nicht existiert klingt für mich danach.
Nach Word umzuwandeln habe ich noch nie versucht. Wundert mich aber eigentlich das dass nicht geht. Oder ist deine Quelle ein Word-Dokument?
Die Quelle ist ein Google Docs Dokument und wenn ich dein Skript mit PDF belasse, dann funktioniert es auch ganz normal. Insofern sollte es eigentlich nicht an der Quelle liegen, da ändert sich ja nichts. Die einzige Änderung die ich vornehme, ist die Änderung von PDF zu MICROSOFT_WORD bzw. GOOGLE_DOCS. Bei beidem erhalte ich den Error.
Hmm, ok, dass habe ich natprlich nicht getestet.
Hi Yukio, hast du mittlerweile eine Lösung gefunden? Ich habe das gleiche Problem…
Leider nein 🙁
Hallo Tobias, tolle Funktion! Leider bekomme ich beim Ausführen des Scripts folgende Fehlermeldung: Exception: SpreadsheetApp.getUi() kann aus diesem Kontext nicht aufgerufen werden. (Zeile 5, Datei „Code“). Woran kann das liegen? VG Dirk
Hallo Dirk,
hast du die Berechtigungsanfrage bestätigt? Wurde der Code bei einem Spreadsheet in den Code-Editor eingegeben oder über ein eigenständiges App-Script Projekt? Kannst du die Funktion „onOpen“ vielleicht mal direkt im Code-Editor ausführen und gucken ob die Meldung da auch kommt?
ich habe leider dasselbe Problem! Wie kann ich das lösen?
Der Code-Editor wurde direkt in Spreadsheet ausgeführt?
Top Sache, danke fürs teilen. Eine Frage trotzdem. Ist es auch möglich mehrseitige PDF’s zu erstellen?
Danke und viele Grüße
Danke für das Feedback. Mehrseitige PDFs sind absolut kein Problem.
Kannst Du mir einen Tipp geben was ich wo ändern muss, damit ich mehrseitige PDF’s erhalten? Zur Erklärung, ich hätte einen Brief der an x Empfänger gehen und ich hätte gern diese x Briefe in einem PDF.
Achso, es sollen mehrere Dokumente in einem Angelegt werden. Dass ist nur mit einer Script-Erweiterung möglich. Über App Script lassen sich sicherlich mehrere PDFs zu einem Zusammenfassen.
Alles klar.
Dann komme ich aktuell so damit klar. Bei Gelegenheit schaue ich mir das Script mal.
Danke schön.
Hi Tobias, vielen Dank für den hilfreichen Input. Bei mir erscheint die Fehlermeldung: Exception: Invalid argument: id. Ich habe die Doc-ID jetzt schon 3x geprüft. Hast du eine Idee, woran das liegen könnte? Gibt es ein Problem, wenn in einem Dokument nicht alle Platzhalter, die im Sheet angegeben sind, vorhanden sind?
Hat er bei Meldung zufällig noch die Zeilennummer angegeben, an welcher Stelle im Code das Problem entstanden ist? Platzhalter die im Sheet nicht drin sind, sollte er ignorieren.
Wie sieht die Id aus? Gerne können Sie mir auch privat eine Mail schreiben. Dann versuche ich das Problem zu verstehen: mail [at] tobias-sell.com
Vielen Dank für die schnelle Rückmeldung. Ich habe dir eine Mail geschickt.
Ich habe das gleiche Problem. Bei mir steht auch unter Details einfach nur: „Exception: Ungültiges Argument: id“
Hatte auch den Fehler „Exception: Ungültiges Argument: id“
Lösung: im Google Sheet mit euren Input Daten muss das Feld „template“ in Zelle A1 stehen.
Ich hatte zunächst das „Template“ in B2 gestellt und den Fehler erhalten. Nachdem ich alles auf Start in A1 gestellt hatte, lief es problemlos – Vielen Dank!
Hallo Tobias,
danach habe ich gesucht! Danke. Wie können denn alle pdf in dem Ordner dann automatisiert an die einzelnen Empfänger geschickt werden?
Hallo Klaus,
sofern du GMail benutzt, kann der Script um eine Zeile mit der GmailApp-Funktion erweitert werden, die verschickt die Mails und unterstützt auch Anhänge
Hallo, bei mir sagt er „Exception: Das Argument kann nicht null sein: nameDetails“
was kann ich tuen ?
Stell sicher, dass alle Spalten ausgefüllt sind.
Hallo Tobias, vielen Dank für deine tolle Lösung! Ich habe allerdings das Problem, dass nach 34 generierten PDF’s die Fehlermeldung kommt, „Maximale Ausführzeit überschritten“ und dann stoppt. Hast du einen Tipp, wie sich das umgehen/lösen lässt?
Hi Benjamin,
Ja, Mist. Da macht Google dann irgendwann zu. Eine Möglichkeit wäre, dass man nur die markierten Zeilen in PDFs umwandelt und so dann mehrere „Batches“ a 30 pdfs macht.
In
for(var row=1; row würde man dann die ActiveRange nehmen.
(Wie) muss ich dazu Zeile 20 abändern?
for(var row=1; row<range.length; row++){ //Durchläuft alle Zeilen bis auf die Überschriften (daher row=1)
Und dann einfach die Spalten in der Tabelle markieren und im Skripteditor auf Create Documents, oder wie ist da der Ablauf?
Nur die Zahl anpassen und es hat funktioniert. Danke nochmals!
Hallo, den Fehler mit der Ausführungszeit kann ich hiernach leider nicht beheben, habe das gleiche Problem. nach ca. 120 von 600 Positionen bricht das Skript ab. Kann ich das Vorgehen einfacher übersetzt bekommen, das zu vermeiden? Bin leider nicht ausreichend bewandert… Dank vorab.
Hallo Klaus,
ich werd die Tage mal schauen, ob ich das in „Batches“ verteilen kann. Kann momentan nichts versprechen.
Tolle Funktion und super erklärt! Leider klappt auch bei mir nicht der import in ein Googel Doc… bei mir kommt folgender Fehler: Exception: Unerwarteter Fehler beim Abrufen der Methode oder Eigenschaft „getFileById“ auf dem Objekt „DriveApp“. Er erstellt dann im Zielordner eine Kopie des Dokuments mit den importierten Daten aber das funktioniert nur für die erste Zeile…
Hi Steffi,
Hast du in der Template Zeile überall eine gültige Template Id? Hast du den Script einmal im Editor ausgeführt und alle Berechtigungen angenommen?
Als letztes kannst du auch das Dokument nochmal neuladen. Manchmal hängt es daran.
Hi Tobi,
ich habe grade alles nochmal neu aufgebaut und getestet. Die pdf’s werden auch wunderbar erzeugt, aber wenn ich dann im Script den Typ auf google_docs ändere, kommt beim Ausführen des Serienbriefs folgender Fehler: „Exception: Blob-Objekt muss für diesen Vorgang eine Datenquelle, die nicht null sein darf, aufweisen.“
Er legt dann eine Kopie des Ursprungsschreibens im gen-Ordner an, aber auch hier nur vom ersten Datensatz…
Hi Steffi, das ist merkwürdig. Kannst mir gerne auch dein Dokument freigeben (oder eine anynymisierte Kopie). Dann kann ich versuchen nachzuvollziehen, woran das liegt. (freigabe an mail at tobias-sell.com)
Das ist total nett, aber das sind geschäftliche bzw. vertrauliche Daten, die kann ich nicht freigeben…
Dann baue ich den Brief doch im Word auf…
Viele Grüße
Steffi
Super Idee und klappt auch toll, aber wie kann ich das ganze per gmail an die Kunden versenden? Danke für die Info
Wenn du Gmail verwendest, kann man die man mit der Gmail-Funktion bei App Scripts das Ganze sehr schnell umsetzen.
Hi Tobias
Echt coole Sache!
Bei mir gibts die Fehlermeldung: Exception: Ungültiges Argument: id. Keinerlei weiteren Details.
ID hab ich kopiert, wo nachfolgend HIER GENOMMEN steht. https://docs.google.com/document/d/HIER GENOMMEN/edit
Kannst du mir weiterhelfen?
LG Nadja
Das ist merkwürdig, das müsste so eigentlich gehen. Ich werd mal schauen, dass ich den Beitrag die Tage mal überarbeite und mögliche Fehlerquellen ausmerze.
Hey, dieses Problem tritt auch bei mir leider auf. Hast Du dazu schon eine Lösung?
Bitte drauf achten, dass der Source-Code wie im Video vollständig ersetzt wurde und auch gespeichert.
Hallo, ja das habe ich bereits getan. Der Fehler bleibt weiterhin bestehen, auch nachdem ich den Script von script.gs kopiert habe
Was wenn ich bei mir im Script Editor nur myfunction und nicht Openfunction zur Auswahl habe?
Der Sourcecode im Editor muss komplett ersetzt werden. (Siehe Video)
Hallo, super Script, danke. Gibt es schon eine Lösung, um für markierte Zeilen einen Serienbrief zu erstellen? Ich komme mit ActiveRange nicht klar, bitte um etwas Hilfe.
Hallo Jörg, da bin ich leider bisher nicht zu gekommen.
Vielen Dank für das hilfreiche script! Mir würde es auch helfen, wenn es sich auf markierte Zeilen eingrenzen ließe. Ich komme mit ActiveRange auch nicht weiter.
Vielen Dank für die super hilfreiche Anleitung. Ich bekomme jedes Mal die Fehlermeldung: Exception: Das Argument kann nicht null sein: name.
Ich bin hier etwas überfragt, was das bedeuten soll. Alle Angaben im Sheet sind vorhanden.
Hast du hier vielleicht eine Lösung?
Hi Stephanie,
wichtig ist, das alle Felder ausgefüllt sind.
DAS Problem hatte ich auch… Ich war allerdings zu faul und hab in Spalte 2 nur „file“ anstatt „filename“ als Überschrift geschrieben… Bei den ersten beiden Spalten muss also sowohl Reihenfolge als auch Bezeichnung korrekt sein!
Ich muss echt mal schauen, dass ich in den Script noch ein paar Abfragen reinpacke. Es gibt doch einige Fehlerquellen, in die man als Nutzer unbewusst reinlaufen kann.
Hallo. Ich würde das Skript vereinfachen. Eine Variable für die ID der Vorlage, eine Variable für den Verzeichnisnamen (irgendwas anderes anstatt „gen“), und noch eine Variable für den Namen der Ausgabedatei – und diese dann in jedem Durchlauf mit der fortlaufenden Nummer (i) versehen. Dann braucht man in der Sheet-Datei kein extra Spalten machen, sondern kan normal mit Anrede, Name usw. arbeiten… Da ich die Vorlagen in Docs erstelle, habe ich den Slides-Teil auch gleich entfernt… Was ich noch nicht probiert habe, aber durchaus sinnvoll wäre: die einzelnen Serienbriefe als PDF zu verketten, so dass man am Schluss nur 1 PDF hat zum drucken…
Vielen Dank für die Erklärung! Nur habe ich eine Frage. Im Text steht, dass wenn filename leer ist, keine Datei erstellt wird. Jedoch wird die erstellt mit dem Namen des Briefes der Doc Datei.
Vielen Dank für den Hinweiß, dass muss ich mir dann nochmal anschauen
@Steffi und @Yukio Iwamoto
Hier mein Antwort auf eure Beiträge, die mir jedoch nie angezeigt wurden:
Hey, ich hatte das gleiche Problem und habe mich mal etwas genauer mit dem Skript auseinander gesetzt.
Wenn du den folgenden Teil entfernst:
“
//Create pdf export
var pdfFile=getTargetFolder().createFile(
DriveApp.getFileById(document.getId()).getAs(MimeType.PDF),
).setName(dataObject.filename);
//Originaldokument löschen
document.setTrashed(true);
“
und dafür
“
//Originaldokument umbenennen
document.setName(dataObject.filename);
“
einfügst bleibt das Dateiformat der Vorlage erhalten. Für mich war das genau die Lösung die ich brauchte, da so meine Präsentation einfach mit den Werten der Tabelle gefüllt wird ich diese danach aber noch bearbeiten konnte.
Also wenn du jetzt die Daten aus einem Google Docs ziehst solltest du dann auch ein Google Docs als Ergebnis erhalten :-).
Ich hoffe ich konnte helfen 😉
Und @tobisell ganz vielen Dank für dieses Anleitung!!!
Hallo, ich habe hier das Problem, dass ich wenn ich den Serienbrief erstellen will immer die gleiche Fehlermeldung kommt.
Exception: Unerwarteter Fehler beim Abrufen der Methode oder Eigenschaft „getFileById“ auf dem Objekt „DriveApp“.
Kann mir da jemand helfen?
Wichtig ist, dass jede Zeile eine Template Id hat
Hi Tobi, ich bin überfragt. Bekomme seit ewigkeiten diese Meldung, habe alles mögliche versucht. Woran kann das liegen?
Exception: Unexpected error while getting the method or property getFileById on object DriveApp.
Stelle sicher, das das Feld „Template Id “ immer mit einer gültigen ID versehen ist, bei allen Zeilen.
Werd das Script bald mal anpassen, weil da so viele reinlaufen.
Hey Tobias, klasse Script. Funktioniert 1a! Hast Du evtl. eine Lösung wie man auch ein Bild/Screenshot aus der Tabelle in den Serienbrief bekommt?
Danke und VG Maik
Hi Maik,
bis jetzt noch nicht gemacht. Man müsste eine eigene Funktion dafür schreiben, das die Bilder erkennt und ersetzt. Habe ich bis jetzt noch nicht gemacht.
Meinst Du, das Du sowas zaubern könntest? 😕 Wir sind hier total lost…
Mailt mich hierzu mit einem Beispiel gerne mal an.
Hallo Tobias,
wirklich super Skript – und sehr flexibel einsetzbar! Ich würde als Template sehr gerne ein Google Sheets Template verwenden. Leider scheitere ich allerdings bei der Umsetzung. Kannst du kurz Hilfestellung geben, wie der Code hierfür aussehen müsste?
Gruß
Christopher
Hallo Christopher,
aktuell ist bei mir leider sehr viel los. Ich nehm das aber mal auf meine „Blogging Liste“ mit auf
Alter Schwede,
das ist ja eine coole Implementierung!
Damit erstelle ich automatisch meine Anmeldezettl für unser Repaircafé aus den Anmeldungen in sheets.
Vorher hab ich immer die Tabelle als csv exportiert und in Word einen Serienbrief damit gemacht.
Eine Frage hätt‘ ich noch:
Das Script erzeugt für jeden Eintrag ein separates PDF. Wäre es auch möglich ein Gesamt-PDF mit allen zu erzeugen?
… entweder direkt oder danach alle PDFs im Ordner zu „kombinieren“?
Gruss HaPe
Hallo Hans-Peter,
vielen Dank für dein Feedback. Ein gesammt PDF müsste man neu schauen, denke es ist möglich mehrere Google Documente zusammenzukopieren und anschließend als PDF zu exportieren. Über Google Script direkt PDFs miteinander zu kombinieren habe ich bisher nicht geschafft.
Lieber Tobias
Als Präsidentin eines Sportvereins habe ich GoogleDrive entdeckt. Nun habe ich Dich entdeckt – auf der Suche, um Serienbriefe zu erstellen. Meine Güte, war das ein gutes Gefühl, als ich es tatsächlich schaffte Deine Anweisungen umzusetzen und meine Serienbriefe tatsächlich funktionierte. Und das als Super-Anfängerin in der Programmier-Kunst.
Herzlichen Dank.
Gruss, d’Andi
Hallo Tobias, tolles Skript und vielen Dank, dass du es teilst. Eine Frage: wie kann ich alle Briefe in einer PDF Datei zusammenfassen? Ich habe über 100 Briefe und hab keinen Nerv jedes einzeln auszudrucken. Liebe Grüsse Patricia
Hi Patricia, das ist von Google Seite aus meineswissens nicht möglich. Es gibt aber für den Desktop tools dafür soweit ich weiß.
Sowas geht schon mit Google Apps Script. Das Add-on „Docs Creator“ hat z.B. so eine Druckfunktion. So kann man die erzeugten Briefe in ein Google Doc / PDF packen. Einfach mal probieren. Add-on siehe: https://workspace.google.com/marketplace/app/google_docs_creator_serienbriefe_addon/77808591299?hl=de
DAS ist ein bisschen nachteilig an dem Skript. Ich hab einfach alle PDFs heruntergeladen und dann im „Foxit PDF Reader“ alle zum Batch-Druck markiert. So kann man alle auf einmal mit den gewünschten Einstellungen drucken, ohne jedes einzeln öffnen zu müssen…
Hi Tobias,
super Script.
Ich möchte nur markierte Briefe erstellen. Wenn ich die Liste erweitere soll er n ur die neu dazu gekommen Briefe erstellen.
Hast Du eine Idee?
Vielen Dank und viele Grüße,
Jens
Hallo Jens,
das ließe sich auf jeden Fall umsetzen. Am besten mit einer eigenen Spalte, wo die erzeugten Dokumente verlinkt weden. Dann könnte der Script auch erkennen, ob die Dokumente bereits erzeugt worden sind.
Hallo Tobi, kannst du hierfür ein paar Hinweise geben, wie man das Skript dementsprechend anpassen könnte? Die Funktion scheint doch sinnvoll zu sein 🙂
Hallo Tobi,
das wäre genial! Ist das schwer zu programmieren?
Ich habe leider nicht das Wissen…
VG,
Jens
Sehr komplex ist es nicht. Man müsste den Script entsprechend anpassen. Hab leider gerade keine Kapazitäten dafür, sonst würd ich den Blog-Beitrag nochmal erweitern.
Das Tool ist wirklich sehr hilfreich und gut.
Ich würde es auch begrüßen, wenn bereits erstellte PDFs nicht noch einmal erstellt werden oder gefragt wird, ob diese überschrieben werden sollen, da sonst der gen-Ordner schnell überläuft (wenn nicht gelöscht wird).
Auch würde ich es begrüßen, wenn nur für die markierten Zeilen ein PDF erzeugt wird.
Wenn das Tool so erweitert werden würde, wäre das richtig gut!
Ich habe ein Kontaktformular mit einer Tabelle auf dem Drive gekoppelt. Es hilft hier sehr weiter, wenn ich aus den Kontaktdaten PDFs erstellen kann oder auch fertige Mails.
Finde es recht spannend, was sich hier bei der Google Suite gerade tut.
Hallo Tobias,
nach langer Suche endlich ein Script für Serienbriefe gefunden. Vielen Dank!
Wie könnten man die Ausführung des Scripts durch eine einkommende bzw gelabelte E-Mail (Gmail) oder neue Spreadsheet Row automatisch ausführen lassen?
Viele Grüße
Philipp
Ein solcher Trigger wäre über Webhooks oder Interval tatsächlich möglich. Auch über Formulareinsendungen.
Hallo Tobias,
noch einmal vielen Dank für das Tool.
Ich pflege die Website eines kleinen Karnevalsvereins und dort soll ein neuer Online-Shop für Vereinsartikel eingerichtet werden. Mit Google Suite und deiner Serienbrief-Funktion komme ich damit schon recht weit bei der Herstellung von automatisierten Rechnungen. Mit etwas Tüfteln habe ich es auch geschafft, aus Bankverbindungsdaten etc. einen Giro-QR-Code zu entwickeln, der als Bild in einer Tabellenspalte eingefügt ist. Leider ist es nicht möglich, das auch dieses Bild des QR-Codes in den Serienbrief auf Rechnungen eingefügt wird.
Gibt es dafür eine Lösung, auch Zellinhalte, die als Bilder in einer Tabelle hinterlegt sind, mit einer Abwandlung des Scripts in einen Serienbrief einzufügen?
Besten Dank!
Bilder die als URL verfügbar sind, lassen sich einfügen. Ggf ist es möglich, die QR Codes als Base64-Codierter String aus dem Excel zu laden und in das Textdokument einzufügen.
Hallo Tobias
Top Script! Eine Anpassung wäre noch super. Bei mir wird die Tabelle immer fortlaufend gefüllt, sprich ich brauche nicht immer alle Zeilen als PDF, sondern nur die neusten. Besteht die Möglichkeit, dass er nur einen Serienbrief mit denjenigen erstellt, welche auch die template ID von Docs haben?
Kannst du mir da die Änderung schreiben und was ich ersetzen muss?
Vielen lieben Dank dir 🙂
Zeitlich gerade eng, muss mal gucken wann ich dazu einen Beitrag schreiben könnte. Grundsätzlich könnte man überlegen, ob man nicht die „SelectedRange“ zum Erstellen nimmt. Dann kann man durch das markieren der Zeilen enscheiden, aus welcher Zeile eine Datei exportiert werden soll.
Hallo Tobias,
super Hilfe! Funktioniert einwandfrei! Sehr lehrreich .. dickes MERCI für deinen Beitrag! Vielen lieben Dank dir
Hallo,
Vielen Dank für den Beitrag aber aus irgendeinem Grund bricht das Skript ab….
„Invalid Argument ID“, Code 76/121/33
Ich habe ein eigens Template verwendet, die id wie beschrieben hinterlegt aber leider geht nichts. Evtl kannst du ja helfen
Hi Ben,
mir scheint, dass dort eine falsche Template ID eingetragen ist. Die Template ID sollte die ID des Dokuments sein und keine Slashes (/) beinhalten
Hallo Tobias,
zu allererst Mal Danke für Deine Arbeit und Erklärungen.
Leider gab es bei mir gleich zwei Probleme. Das erst war, dass die Funktion „renderMenu“ nicht funktioniert hat, was aber grundsätzlich nicht weiter schlimm ist, das ich die Scripte für meine Kollegen eh lieber mit einem Button starten lasse. Allerdings bekomme ich beim „manuellen“ Aufrung der Funktion startOver die folgende Fehlermeldung:
TypeError: Cannot read property ‚getDataRange‘ of null
Kannst Du Dir vorstellen, woran das liegen könnte?
Gruß,
Marcus
Hallo Marcus,
ja, ich glaube es liegt daran, dass er „ActiveDataRange“ nutzt. Du musst sicherstellen, dass das Korrekte Tab im Sheet geöffnet ist wenn du die Funktion ausführst.
Hey Markus,
ich versuche seit Stunden diesen serienbrief zu erstellen. Mein problem: ich kopiere den code bei apps scrip rein, speicher es und führer es dann aus wie du erklärt hast. Dann fragt apps script für welches konto ich das ausführen möchte. wenn ich dann aber mein Konto auswähle kommt die meldung: App wurde Blockiert. Die Meldung kann ich dann nur schließen und das wars. Mit meiner persönlichen Google-Konto funktioniert das ganz., leider aber nicht mit unserer Firmen-google-account.
ich bin jetzt zig Foren durch gegangen finde aber nichts. Ich freue mich auf deine Antwort.
Besten Dank im Voraus Max
Hi Max,
ich vermute fast, dass das dann eine Sicherheitseinstellung in dem Firmenkonto ist. Die Ausführung von App Scripts lässt sich durch den Administratorenbereich anpassen.
Hi Tobi,
ganz lieben Dank für deine Mühe und das coole Skript.
Vielleicht hast du eine Idee, wo ich noch schrauben kann, damit es funktioniert. Das Skript läuft durch. Generiert allerdings nur Dateien mit dem Dateinamen der Ursprungsdatei und auch nur im Docx Format statt im PDF. Die Arrays stehen auch noch als {{date}} etc. drin, werden also nicht ersetzt.
Ich weiß, das ist sehr unspezifisch, vielleicht hast du ja trotzdem eine Idee.
Danke,
Eva
Bekommst du irgendeine Fehlermeldung?
Hallo Tobias,
vielen Dank für die Erklärung. Ich habe das gleiche Problem wie Eva, gibt es dazu schon ein Lösung? Eine Fehlermeldung habe ich nicht bekommen, allerdings, wie oben erklärt, wurden auch meine Platzhalter nicht mit den gewünschten Daten ausgefüllt sondern der eigentliche Brief immer wieder gleich erstellt.
Vielen Dank
Jasmin
Das ist echt merkwürdig. Sind die Platzhalter wirklich so geschrieben wie die Überschrift in der ersten Zeile vom Google Sheet? (Bis auf die Klammern)?
Ja eigentlich schon.
Beim Kopieren des App-Scripts oben, nehme ich die BATCH-Size mit und füge statt der 50 die Anzahl ein, die ich an Briefen brauche, oder nicht?
Nein, er macht alle. Irgendwann gibt es nur ein TimeOut, dann setzt er automatisch an der Stelle fort, wenn man den Script erneut startet. Sollte es zu einem Timeout kommen muss man die Batch-Size niedriger wählen. (passiert, wenn man sehr viele Platzhalter oder sehr große Dokumente hat)
Hallo Tobias,
danke für’s Teilen des Skriptes!
Ich habe das gleiche Problem, wie Jasmin und Eva – habe alle Schritte exakt befolgt und es kommt keine Fehlermeldung. Trotzdem wird das Beispieldokument (für zwei Testzeilen im Excel) je einmal als .docx im Gen-Ordner angelegt und die {{parameter}} bleiben un-ersetzt.
Hast du eine Idee, woran dies liegen könnte?
VG
Jens
Hey,
kurze (zweite) Rückmeldung. Ich hatte folgendes Problem.
Ich habe alle Schritte wie beschrieben durchgeführt, aber es wurden lediglich soviele google docs wie Zeilen im Excel generiert, ohne dabei die Platzhalter zu tauschen oder das als PDF abzulegen.
Das Skript warf keine Fehlermeldungen.
Die Ursache habe ich nun (95%) gefunden – das Ursprungsdokument war eine importierte docx Datei. Mit einer neu erstellten Googledoc Datei als template hat nun alles gut funktioniert.
VG
Jens
Spannend, schätze mal das die Template Tags beim importieren irgendwie umformatiert wurden oder sich anschließend in Absätzen befinden, die durch das einfache Suche und Ersetzen nicht gefunden werden. So wie ich es verstehe, funktioniert dann aber alles bei dir
Auch late to the party, bei mir war es ein importiertes docx. Ein frisch erstelltes Google Doc funktioniert!
Hallo Tobias,
glücklicherweise gibt es Menschen, wie Dich, die andere an ihrem Wissen teilhaben. Vielen Dank für das Tutorial. Es war sehr hilfreich und funktioniert tadellos.
Gibt es eventuell eine Möglichkeit das Script so anzupassen, dass man statt dem PDF-Export direkt auf einen Drucker zugreift bzw. den Seriendruck gleich ausdrucken kann? Leider bin ich in der Scriptologie nicht so fit, dass ich Diesen entsprechend selbst anpassen könnte.
//Create pdf export
var pdfFile=getTargetFolder().createFile(
DriveApp.getFileById(document.getId()).getAs(MimeType.PDF),
).setName(dataObject.filename);
//Originaldokument löschen
document.setTrashed(true);
Nochmals vielen vielen Dank 🙂
Hi Patrick,
danke für die Lorbeeren 🙂 . Aus dem App-Script einen Drucker anzusprechen ist leider nicht möglich. Man könnte aber einen Umweg über eine andere API nehmen. Z.B:
– Versanddienstleister: Dann könnte man direkt auch den Brief verschicken lassen
– Google Cloud Print: Darüber könnte man jeden beliebigen Drucker nutzen
Für beides gibt es aber noch nichts fertiges. Das müsste man basteln.
Gruß
Tobias
Hallo Tobias,
ich arbeite erst neuerdings mit Google Sheets und bin begeistert, wie schnell ich es geschafft habe den Serienbrief zu erstellen, super Anleitung.
Ich habe das ganze vorerst in einem Versuchsdokument ohne Probleme anwenden können. Nun wollte ich es in das Dokument meines Chefs integrieren, jedoch finde ich dort in der Menüleiste keine Option „Erweiterungen“. Zuerst dachte ich es liegt an dem Eigentümer des Dokumentes aber auch er hat keine „Erweiterung“ als Option. Ich habe versucht über Google eine Antwort zu bekommen, leider ohne Erfolgt.
Hast du eine Idee, wo das Problem liegt?
Liebe Grüße,
Insa
Ist bei ihm vielleicht Englisch als Sprache eingestellt? Dann such mal nach Extensions.
Ansonsten kannst du mir gerne auch eine email mit einem Screenshot schicken. Grüße Tobias
Hi Tobias,
danke für den Blog und das Script. Funktioniert einwandfrei.
Nutze es zu erstellen von Lebensläufen.
Hast du eine Idee wie man Bilder an einer bestimmten Position einsetzen bzw. ersetzen könnte?
LG
Gerald
Hi Gerald,
das ist ja auch eine interessante Idee. Bilder könnte man ggf über das Alt-Tag von Bildern machen.
Super, genau das hab ich gerade gebraucht, vielen DANK!!!
Das Skript ist echt klasse! Vielen Dank dafür. Gibt es eine Möglichkeit, dass nicht jedes Mal alle Dokumente erzeugt werden, sondern nur das, von dem die Daten neu hinzugefügt worden sind?
Man könnte bei
var range=SpreadsheetApp.getActiveSheet().getDataRange().getDisplayValues();
statt
getDataRange()
getSelection().getDataRange()
nehmen, dann müsste er nur die markierten Felder nutzen. Habe ich aber noch nicht ausprobiert.Ich habe ein ähnliches Problem, dass in meinem sheet nicht alle Zeilen eine template-id haben (mache noch diverse Berechnungen in dem sheet).
Kann man nicht prüfen, ob die aktuelle Zeile dieses Feld gesetzt hat und wenn nicht, dann überspringen?
Somit könnte man dann auch steuern, dass nicht alle Zeilen bearbeitet werden, sondern nur die, die auch eine template-id haben
ok, das geht anscheinend ganz einfach: in der funktion spreadSheetToObjects() unterhalb von
for(var row=1; row<range.length; row++){ //Durchläuft alle Zeilen bis auf die Überschriften (daher row=1)
folgende Prüfung vornehmen:
var firstCol = range[row][0];
if(firstCol.length == 0) {
continue;
}
Hi, viele Dank dafür! Ich finde das Script super und nutze es auch.
kurze Frage: was muss ich im Code ändern damit er die Datei(en) nicht nur im PDF Format sondern auch im Docs Format mit dem gleichen Namen wie die PDF Datei speichert?
Das wird eigentlich unter „getAs“ gemacht. Allerdings ist angeblich Docs nicht erlaubt.
//Create pdf export
var pdfFile=getTargetFolder().createFile(
DriveApp.getFileById(document.getId()).getAs(MimeType.PDF),
).setName(dataObject.filename);
//Originaldokument löschen
document.setTrashed(true);
}
Kannst ja mal versuchen statt
MimeType.PDF
einfach „application/msword“ oder „application/vnd.openxmlformats-officedocument.wordprocessingml.document“ zu verwendensprich
DriveApp.getFileById(document.getId()).getAs("application/msword"),
Vielen Dank für das tolle Skript + der sensationellen Erklärung im Video.
Es hat mir den Tag gerettet, sonst hätten wir einige Zertifikate manuell anpassen müssen 😀
Hey, bin guter Dinge, endlich ein vernünftiges Script gefunden zu haben! 🙂 Allerdings habe ich einen Fehler zu dem ich hier in den Kommentaren noch nichts gefunden haben:
„Exception: Unerwarteter Fehler beim Abrufen der Methode oder Eigenschaft „replaceAllText“ auf dem Objekt „SlidesApp.Slide“.“
Was ist zu tun?
Klingt fast dannach, das der Script für Google Docs in Google Presentation verwendet wurde. Kannst du das nochmal prüfen?