Send email from a sheet
MailApp.sendEmail(to, subject, body) sends a plain email from the account running the script; GmailApp.sendEmail does the same and also supports reading mail. Use the options object for htmlBody, cc, bcc, name, replyTo and attachments. To mail a list, read the rows with getValues and send one email per row, then record Sent in a column.
Syntax
MailApp.sendEmail(to, subject, body, {htmlBody, cc, bcc, name, replyTo, attachments})| Part | What it is |
|---|---|
to | One address or several separated by commas |
subject, body | Plain text subject and body |
htmlBody | HTML version of the body, shown when the client supports it |
attachments | Array of Blobs, for example DriveApp.getFileById(id).getBlob() |
MailApp.getRemainingDailyQuota() | Emails you may still send today |
Examples
MailApp.sendEmail("ana@example.com", "Weekly numbers", "Total this week: 317");Result: A plain text email arrives at ana@example.com
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Contacts");
const rows = sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues();
rows.forEach((r, i) => {
const [name, email, status] = r;
if (status === "Sent" || !email) return;
MailApp.sendEmail(email, "Hello " + name, "Hi " + name + ", your report is ready.");
sheet.getRange(i + 2, 3).setValue("Sent");
});Result: One email per unsent contact; column C marked Sent so a rerun skips them
GmailApp.sendEmail("ops@example.com", "Invoice 1042", "See attached.", {
htmlBody: "Invoice 1042 attached.",
attachments: [DriveApp.getFileById("1AbC...xyz").getBlob()],
cc: "finance@example.com",
name: "Billing bot"
});Result: HTML email with the Drive file attached and a cc
Common mistakes
- Service invoked too many times for one day: email: you hit the daily quota; check getRemainingDailyQuota first and send fewer, or combine recipients in one message.
- Invalid email: an empty or malformed cell in the email column; skip rows where the address does not contain @.
- Running onEdit sends nothing: the simple trigger cannot send mail; use an installable trigger or a menu item.
- Mail arrives as raw HTML text: you put HTML in body; put it in htmlBody and keep a plain fallback in body.
Practice Send email from a sheet
These drills check your answer on live data, free, no account needed.
- Run tasks in sequence`tasks` is an array of functions; each returns a Promise or a plain value. Run them one at a time, in order, starting each only after the previous one has finished, and resolve with the array of their results. If a task fails, reject and do not start the remaining tasks. Tests run on a fake clock: `setTimeout`, `clearTimeout` and `Date.now` are simulated, so waits are instant and exact.
- Full namesEach user looks like `{ first, last }`. Return an array of full names: first and last joined by one space. Some people have no last name (an empty string, or no `last` field at all); for them return just the first name, with no trailing space.
Questions
MailApp or GmailApp?
MailApp only sends and needs fewer permissions, so prefer it for notifications. GmailApp is needed to read, search, label or send with aliases and drafts.
Can I send from a different address?
Only from an alias configured in Gmail settings, via GmailApp.sendEmail with the from option set to that alias. Otherwise set a display name with name and a replyTo.
Related
- Gmail search and labelsAutomate Gmail with Apps Script: GmailApp.search with Gmail query syntax, loop threads and messages, read subject, sender and body, apply labels, archive.
- Loop through rowsLoop through rows in Google Sheets with Apps Script: read the data once with getValues, loop the 2D array with for, for...of or forEach, then write back.
- Installable triggersInstallable triggers in Google Apps Script: how they differ from simple triggers, creating them from the UI or ScriptApp.newTrigger, onChange, onFormSubmit.
- Time-driven triggersSchedule Google Apps Script with time-driven triggers: create them in the Triggers panel or with ScriptApp.newTrigger, run daily, hourly or weekly, delete.