A sheet of recipients and a template, and a human who still wants to read every email before it goes out. createDraftsFromRows(rows, buildFn) builds one Gmail draft per row (to, subject, HTML body), skips blank emails, and returns { created, skipped } so you can log how many landed in Drafts.
When to use
- Batch outreach where a person hits Send after a quick review
- Reminder or renewal emails that need a last look at the subject line
- Any flow where sending straight from the script is one mistake away from a blast
How to use this snippet
/**
* Create Gmail drafts from row objects. Skips blank `to`.
*
* @param {Object[]} rows
* @param {function(Object): {to: string, subject?: string, htmlBody?: string, body?: string, cc?: string, bcc?: string, name?: string, attachments?: *}} buildFn
* @return {{created: number, skipped: number}}
*/
function createDraftsFromRows(rows, buildFn) {
var created = 0;
var skipped = 0;
(rows || []).forEach(function (row) {
var msg = buildFn(row);
if (!msg || msg.to == null || !String(msg.to).trim()) {
skipped++;
return;
}
var to = String(msg.to).trim();
var subject = msg.subject == null ? '' : String(msg.subject);
var htmlBody = msg.htmlBody != null ? String(msg.htmlBody) : (msg.body == null ? '' : String(msg.body));
var options = { htmlBody: htmlBody };
if (msg.cc) options.cc = msg.cc;
if (msg.bcc) options.bcc = msg.bcc;
if (msg.name) options.name = msg.name;
if (msg.attachments) options.attachments = msg.attachments;
GmailApp.createDraft(to, subject, htmlBody, options);
created++;
});
return { created: created, skipped: skipped };
}
Example
function draftRenewals() {
var sh = SpreadsheetApp.getActive().getSheetByName('Renewals');
var data = sh.getDataRange().getValues();
var headers = data[0];
var rows = data.slice(1).map(function (r) {
var o = {};
headers.forEach(function (h, i) { if (h) o[h] = r[i]; });
return o;
});
var result = createDraftsFromRows(rows, function (row) {
return {
to: row.Email,
subject: 'Renewal — ' + row.Client,
htmlBody: '<p>Hi ' + row.Contact + ', your plan renews on ' + row.RenewalDate + '.</p>'
};
});
console.log(result.created + ' drafts, ' + result.skipped + ' skipped');
}
Tips:
- Drafts land in the running user's Gmail Drafts. Run it as the person who will hit Send.
- Blank or whitespace-only
tovalues are skipped, not errors. Useskippedto catch empty Email columns. - Optional
cc,bcc,name, andattachmentson the built object are passed through toGmailApp.createDraft. - This does not send. Pair with a human review pass, or a later script that sends only drafts you've tagged.
Tip: NitroGAS Co-Pilot can turn your template Doc or HTML into the buildFn. Paste one sample row.
Happy Coding!
