Skip to content
// Gmail · Apps Script

MailApp vs GmailApp in Apps Script.

MailApp and GmailApp both send email from Apps Script, but they ask for very different OAuth scopes. Here is when to use each, and why the quota counter does not care which one you picked.

I want to send email from Apps Script without granting my script full mailbox access, and I am not sure whether to use MailApp or GmailApp.

The script

copy · paste · trigger
send_scoped.gs
Apps Script
// Choose MailApp when you only need to send.
// Choose GmailApp when you also need to read or search.

function sendWithMailApp() {
  MailApp.sendEmail({
    to: '[email protected]',
    subject: 'Weekly report',
    body: 'Attached is this week\'s data.',
    attachments: [SpreadsheetApp.getActiveSpreadsheet().getAs('application/pdf')]
  });
}

function sendWithGmailApp() {
  GmailApp.sendEmail(
    '[email protected]',
    'Weekly report',
    'Attached is this week\'s data.'
  );
}

function checkRemainingQuota() {
  Logger.log('Sends remaining today: ' + MailApp.getRemainingDailyQuota());
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

The scope difference is the whole point

MailApp requests only https://www.googleapis.com/auth/script.send_mail. That is a narrow scope: the script can send email and nothing else. When you deploy a script or share it with a team, users see a consent screen that says 'Send email on your behalf.' That is a much easier yes to click than what GmailApp demands.

GmailApp requests https://mail.google.com/, which is full mailbox access. Read, write, delete, search, label — the whole account is exposed. That scope is appropriate when your script genuinely needs to search threads, create drafts, or read message bodies. It is overkill when you only want to fire off a notification email.

The first time I hit this distinction was on a shared Sheets tool where colleagues balked at the consent screen. Swapping MailApp.sendEmail for GmailApp.sendEmail — same arguments, same result — dropped the consent screen from 'full Gmail access' to 'send email only.' Adoption went from three testers to the whole team in one afternoon.

Quota: one counter, two services

Google's daily send limit applies to both services combined, not separately. For a personal Google account the ceiling is 100 emails per 24-hour rolling window; for a Google Workspace account it is 1,500. Those numbers come from the Apps Script quotas page and have been stable for years, but always verify for your account tier.

MailApp.getRemainingDailyQuota() is the only programmatic way to read the counter before you hit it. GmailApp has no equivalent method. If your script might exhaust the quota mid-run, check the counter at the top of the function and bail early with a useful log message rather than letting the script throw a 'Service unavailable' error at message 101.

Sending via both services in the same script does not give you two quotas. That is the trap. A script that calls MailApp.sendEmail 80 times and then GmailApp.sendEmail 30 times will fail on the 101st call regardless of which service made it.

When GmailApp is actually the right choice

If you need to search for an existing thread and reply to it in-thread rather than sending a new message, GmailApp is the only option. GmailApp.search() returns GmailThread objects; you can call thread.reply() to keep a conversation intact in the recipient's inbox. MailApp cannot do this.

Creating a draft for human review before sending is another GmailApp-only capability. GmailApp.createDraft() lets the script stage a message that a person approves and sends manually. That pattern is useful for scripts that generate content you want to eyeball — digest emails, outreach drafts — without fully automating delivery.

The short rule: if your script's job is purely outbound notification, use MailApp. If it also reads, searches, replies in-thread, or stages drafts, use GmailApp and accept the broader scope.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Can I use MailApp and GmailApp in the same script?
Yes. Apps Script will request the union of scopes both services need. In practice, if GmailApp is present, it already requests full mailbox access, so adding MailApp changes nothing on the consent screen. The combined quota still applies.
Why does my script ask for full Gmail access when I only call sendEmail?
You are probably calling GmailApp.sendEmail rather than MailApp.sendEmail. Both methods accept the same arguments and produce the same result, but GmailApp carries the full https://mail.google.com/ scope. Switch the call to MailApp.sendEmail and the consent screen will narrow to send-only.
Does MailApp.getRemainingDailyQuota() count emails sent by GmailApp?
Yes. The method returns the remaining sends for the entire account for the day, regardless of which service consumed them. It is a shared counter.
What happens when the daily quota is hit mid-script?
Apps Script throws an exception with the message 'Service unavailable: Gmail' or similar. The script stops at that call and does not send the remaining messages. There is no built-in retry or queue; you have to handle it yourself, usually by catching the exception and writing the unsent rows to a sheet for the next run.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.