Send Email With Budget

A nightly job that emails 300 people works fine until the day it doesn't, and then it dies halfway through with “Service invoked too many times for one day: email”. sendEmailWithBudget(message, opts) keeps its own daily send counter in Script Properties, keyed by today's date in the script's timezone. When the next send would go over your budget, it refuses and returns { sent: false, reason } instead of throwing mid-loop, so your job can stop cleanly and pick up tomorrow.

When to use

  • Nightly or weekly email blasts from a sheet
  • Reminder jobs that run on a trigger and might grow past the daily cap
  • Any loop that calls MailApp.sendEmail more than a handful of times

How to use this snippet

/** * Send one email, but never past a daily budget. * Counter lives in Script Properties, keyed by date in the script timezone. * * @param {Object} message same object MailApp.sendEmail accepts ({ to, subject, body, htmlBody, ... }) * @param {Object=} opts { dailyBudget: number (default 90), delayMs: number (default 0) } * @return {{sent: boolean, count?: number, reason?: string}} */ function sendEmailWithBudget(message, opts) { opts = opts || {}; var budget = opts.dailyBudget || 90; // stay under the 100/day consumer cap var props = PropertiesService.getScriptProperties(); var today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd'); var key = 'MAIL_SENT_' + today; var sent = Number(props.getProperty(key) || 0); if (sent + 1 > budget) { return { sent: false, reason: 'Daily budget reached (' + sent + '/' + budget + ')' }; } // Belt and braces: Google's own count includes mail sent by your other scripts if (MailApp.getRemainingDailyQuota() < 1) { return { sent: false, reason: 'Gmail quota exhausted (getRemainingDailyQuota = 0)' }; } if (opts.delayMs) Utilities.sleep(opts.delayMs); MailApp.sendEmail(message); props.setProperty(key, String(sent + 1)); return { sent: true, count: sent + 1 }; }

Example

function sendReminders() { var sh = SpreadsheetApp.getActive().getSheetByName('Reminders'); var rows = sh.getDataRange().getValues(); // Email | Name | SentAt for (var i = 1; i < rows.length; i++) { if (rows[i][2]) continue; // already sent var res = sendEmailWithBudget({ to: rows[i][0], subject: 'Quick reminder', body: 'Hi ' + rows[i][1] + ', your report is due Friday.' }, { dailyBudget: 90, delayMs: 250 }); if (!res.sent) { console.log('Stopping: ' + res.reason); break; } sh.getRange(i + 1, 3).setValue(new Date()); // checkpoint } }

Tips:

  • Set dailyBudget a little under your real quota (100/day on consumer accounts, 1,500 on Workspace) so other scripts and manual tests still have room.
  • MailApp.getRemainingDailyQuota() is Google's own count. Use it to sanity-check the budget before a big run.
  • Pair it with a SentAt column so a stopped run resumes where it left off instead of re-sending.
  • Old MAIL_SENT_ keys are tiny, but you can clear anything older than a week in a monthly cleanup.

Tip: NitroGAS Co-Pilot can swap the MailApp.sendEmail calls in an existing loop for sendEmailWithBudget and add the SentAt checkpoint. Paste the function.

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