Send Digest Per Owner

One big “open items” email to the whole team gets ignored. One short email per person, listing only their rows, gets read. sendDigestPerOwner(rows, ownerKey, renderFn, opts) groups row objects by an owner field (an email address), calls your renderFn(owner, rows) to build each message, and sends one email per owner. dryRun logs instead of sending, and maxSends caps how many go out in one run.

When to use

  • “Your open tickets / tasks / invoices” emails from a shared sheet
  • Weekly reminders where each person only cares about their own rows
  • Any digest that currently goes to everyone and helps no one

How to use this snippet

/** * Group rows by owner and send each owner one email. * * @param {Object[]} rows row objects (e.g. from rowsToObjects) * @param {string} ownerKey field holding the owner's email * @param {function(string, Object[]): {subject: string, body: string, htmlBody: (string|undefined)}} renderFn * @param {Object=} opts { dryRun: false, maxSends: 50 } * @return {{sent: string[], skipped: string[], dryRun: boolean}} */ function sendDigestPerOwner(rows, ownerKey, renderFn, opts) { opts = opts || {}; var maxSends = opts.maxSends || 50; var groups = {}; rows.forEach(function (r) { var owner = String(r[ownerKey] || '').trim().toLowerCase(); if (!owner) return; (groups[owner] = groups[owner] || []).push(r); }); var result = { sent: [], skipped: [], dryRun: !!opts.dryRun }; Object.keys(groups).sort().forEach(function (owner) { if (result.sent.length >= maxSends) { result.skipped.push(owner); return; } var msg = renderFn(owner, groups[owner]); if (opts.dryRun) { console.log('[dry run] to ' + owner + ': ' + msg.subject + ' (' + groups[owner].length + ' rows)'); } else { MailApp.sendEmail({ to: owner, subject: msg.subject, body: msg.body, htmlBody: msg.htmlBody }); } result.sent.push(owner); }); if (result.skipped.length) console.log('maxSends reached; skipped: ' + result.skipped.join(', ')); return result; }

Example

function weeklyOpenItems() { var sh = SpreadsheetApp.getActive().getSheetByName('Tasks'); var rows = rowsToObjects(sh.getDataRange().getValues()) .filter(function (r) { return r.Status !== 'Done'; }); var res = sendDigestPerOwner(rows, 'Owner', function (owner, mine) { return { subject: 'You have ' + mine.length + ' open task' + (mine.length === 1 ? '' : 's'), body: mine.map(function (r) { return '- ' + r.Task + ' (due ' + r.Due + ')'; }).join('\n') + '\n\nSheet: ' + SpreadsheetApp.getActive().getUrl() }; }, { dryRun: true, maxSends: 30 }); console.log(JSON.stringify(res)); }

Tips:

  • Run with dryRun: true first and read the Log before anyone gets an email.
  • Owner emails are trimmed and lower-cased, so Ana@Example.com and ana@example.com get one email, not two.
  • Rows with a blank owner are ignored. Count them separately if they matter.
  • Keep maxSends under your daily quota, or pair with sendEmailWithBudget.

Tip: NitroGAS Co-Pilot can write the renderFn for your columns. Paste your header row and what each person should see.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension