Task guide
Send an email for each row in a Google Sheet
Use one row per person, then let the script send only the rows that have not already been marked as sent.
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.
- Use this only with people who expect to receive the message. Run manually with one operator; do not overlap executions or edit the input rows during a run.
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 where this marker is visible. It is not proof of inbox delivery or protection against every duplicate.
2 · Review before running
What the script can access and change
Permissions you approve
- Read and update the active Google Sheet.
- Send email from your Google account.
Effects of one run
- Calls GmailApp.sendEmail for each row with an email address and no Sent status in the initial read.
- Requests a Sent write in column D after each send call returns. Writing per row records progress so a retry does not repeat an email send; a Sheets-only update should use one batched write instead. Sheets can buffer writes and the two services do not share a transaction.
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 safely
Follow these steps in order
Set up one test row
Add the four headers, then add one row using your own email address. Leave every other recipient out until the test works.
Paste and save the function
Open Extensions, choose Apps Script, replace the starter function with this one, and save.
Run the test
Choose sendSheetFollowUps and run it. Approve access, then confirm that your test email arrives and its row changes to 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, reconcile its results before running again: a send can succeed before its status write fails.
5 · Confirm
How to tell it worked
After a successful run, processed rows show Sent and their messages appear in Gmail Sent. The marker records a completed send call, not confirmed receipt or exactly-once delivery.
If the result is different
Troubleshooting
- The script stops with "Add a Sheet tab named Customers before running this function."
- Rename the Sheet tab to Customers, or change the sheet name in the code and in the editable values.
- A row was skipped.
- Make sure its email address is present and its status cell does not already say Sent.
- A run failed and some status cells are empty.
- Do not rerun blindly. In Gmail Sent, compare the recipient, subject, body, and time with each affected row. Mark a row Sent only after confirming its message. If the outcome is unclear, leave the automation stopped until you decide whether another send is acceptable. Overlapping runs 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
Keep a person and a project note in a Sheet, then create one short Doc per unfinished row.
- Task guide Add a custom menu to a Google Sheet
Use an onOpen() simple trigger and SpreadsheetApp.getUi().createMenu() to add a custom menu bar item that shows which cell is currently selected.
- 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. Reach it with getSheetByName() or getActiveSheet(), then inspect getLastRow() or read cells with getRange().
- 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
Open Extensions > Apps Script from a Google Sheet or Doc to reach the code editor. Replace the starter function myFunction() { }, save with Ctrl+S, press Run, and read what Logger.log printed.
- 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 run repeatable actions across rows.
- 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 track per-message delivery state in Google Apps Script.