Skip to content

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

Change these values

Make the code yours before you run it.
Sheet tab name
Customers

This must match the tab that holds your rows.

Email subject
A quick follow-up

Change it to the subject your recipients should see.

Sent status
Sent

A 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.

Code.gs
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

  1. 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.

  2. Paste and save the function

    Open Extensions, choose Apps Script, replace the starter function with this one, and save.

  3. Run the test

    Choose sendSheetFollowUps and run it. Approve access, then confirm that your test email arrives and its row changes to Sent.

  4. 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

Keep going