Task guide
Send an email for each row in a Google Sheet
Send one email to each person listed on a Customers tab. The script marks each row Sent as it goes, so a later run sends only to rows that are not marked yet.
1 · Prepare
Put these pieces in place
- Create a Sheet tab named Customers.
- Put a name in column A, an email address in column B, an amount in column C, and leave column D for the sent status.
- Send only to people who expect the message. Run the script by hand, and only one person should run it at a time. Wait for each run to finish, and do not edit the rows while it runs.
Change these values
- Sheet tab name
CustomersThis must match the tab that holds your rows.
- Email subject
A quick follow-upChange it to the subject your recipients should see.
- Sent status
SentA later run skips rows that show this word. Sent means the script called sendEmail for that row. It does not confirm the message arrived, and it cannot prevent every duplicate.
2 · Review before running
What the script can access and change
Permissions you approve
- See, edit, create, and delete all your Google Sheets spreadsheets. The code only reads the Customers tab and writes Sent into column D of this spreadsheet.
- Read, compose, send, and permanently delete all your email from Gmail. Google asks for full Gmail access because the code uses GmailApp, but the code only sends one email for each unsent row.
Effects of one run
- Calls GmailApp.sendEmail for each row with an email address and no Sent status in the initial read.
- Writes Sent to column D right after each email is sent, so a later run does not send that row again. The script writes one row at a time, instead of all rows at the end, because each email has already gone out as soon as it is sent. If the run stops between the send and the write, or before Sheets saves the write, the email is sent but column D stays empty.
3 · Copy
Paste this into the Apps Script editor
Create or open a script file, replace the example values marked above, then save the project.
function sendSheetFollowUps() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) throw new Error('Add a Sheet tab named Customers before running this function.');
const firstDataRow = 2;
const lastRow = sheet.getLastRow();
if (lastRow < firstDataRow) return;
const rows = sheet.getRange(firstDataRow, 1, lastRow - firstDataRow + 1, 4).getValues();
rows.forEach((row, index) => {
const [name, emailAddress, amount, status] = row;
if (!emailAddress || status === 'Sent') return;
const greeting = name ? 'Hello ' + name + ',' : 'Hello,';
const message = greeting + '\n\nThis is a quick follow-up about ' + amount + '.\n\nThank you.';
GmailApp.sendEmail(String(emailAddress), 'A quick follow-up', message);
sheet.getRange(firstDataRow + index, 4).setValue('Sent');
});
} 4 · Run
Follow these steps in order
Set up one test row
On the Customers tab, put the four headers in row 1. In row 2, add your own name, your own email address, and an amount, and leave column D empty. Leave every other recipient out until the test works.
Paste and save the function
Choose Extensions, then Apps Script. Replace the starter function with this one and save.
Run the test
Choose sendSheetFollowUps in the function dropdown above the code, then click Run. Approve access when Google asks. Check that your test email arrives and that column D of your row now says Sent.
Add the intended recipients
Only after the test succeeds, add rows for people who expect the message. Wait for the previous run to finish. If it failed, check Gmail Sent before you run again, because an email can be sent even when its row was not marked.
5 · Confirm
How to tell it worked
After a successful run, each processed row shows Sent and its message appears in Gmail Sent.
If the result is different
Troubleshooting
- The run stops with "Add a Sheet tab named Customers before running this function."
- Rename the tab to Customers, or change the tab name in the code to the name of your tab.
- A row got no email and is not marked Sent.
- Check that column B in that row holds an email address and that column D is empty. The script skips rows with no address and rows already marked Sent.
- A run failed and some status cells are empty.
- Do not run the script again yet. In Gmail Sent, compare the recipient, subject, body, and time with each row that has an empty status. Type Sent in a row only after you find its message. If you cannot tell whether a row got an email, do not run the script again until you decide whether a second email is acceptable. Two runs at the same time can also send the same row twice.
Technical details
Names used in this script
Open these details when you want to adapt the example or look up an exact service or method name.
Show Apps Script symbols and service pages
Apps Script symbols
Service pages
Keep going
Related tasks, basics, and apps
- Task guide Create a Google Doc from each Sheet row
Create one short Google Doc for each row on a Projects tab that has no document link yet. The script writes the link to the new Doc back into the row.
- Task guide Add a custom menu to a Google Sheet
Add a menu named Helpful tools to the menu bar of a Google Sheet. Its one item shows which cell is selected. Save the script, reload the Sheet, and the menu appears.
- Object reference Spreadsheet
A Spreadsheet is the whole Google Sheets file. Use SpreadsheetApp.getActiveSpreadsheet() or openById() to get it, then call getSheetByName() or getActiveSheet().
- Object reference Sheet
A Sheet is one tab inside a Google spreadsheet. Get it with getSheetByName() or getActiveSheet(). Use getLastRow() to find the last row with content, or getRange() to select cells to read.
- Object reference Range
A Range represents one cell such as A2 or a rectangular block of rows and columns in a Sheet. Use getValue() for one cell and getValues() or setValues() for a 2D array of rows.
- Basics Run your first Apps Script function
Your first script, from opening the editor to reading its output. You replace the starter function, save, click Run, and read what Logger.log printed in the Execution log.
- Basics Apps Script permissions and authorization
Before a script can read your sheet or send your mail, you give it permission to access a list of OAuth scopes. This page walks through each authorization screen, explains @OnlyCurrentDoc, and shows how to revoke access.
- Basics Execution log, Logger.log, and debugging
Most Apps Script debugging is printing a value with Logger.log or console.log and reading the error line. This page shows where the output goes and how to trace a failure.
- Google app Google Sheets
SpreadsheetApp lets a Google Sheets script read a Sheet or Range, add a custom menu or formula, and act on each row, such as sending one email per row.
- Google app Gmail
GmailApp scripts can send messages, search threads, apply labels, and read attachments after you approve Gmail access for the project.
- Diagnostic Exception: Invalid email
How to spot blank or malformed recipient cells in a Google Sheet before calling GmailApp.sendEmail().
- Book chapter Prepare Gmail work before sending
Search test threads, create plain-text or HTML drafts, and save the state of each message in Google Apps Script.