Skip to the page

Guides

Text each new row in a Google Sheet

Send a text from your own number for every new row in a Google Sheet and write back when it went, with Zapier, Make or a short Apps Script that needs nothing else.

Luis runs Northside Tutoring. Parents sign up through a Google Form, and every answer becomes a row in a sheet called “Sign-ups”. Luis wants each parent to get a text from his own number confirming the class they picked, and he wants the sheet to show which rows were texted and when, so his assistant never texts a parent twice by hand.

What you’ll have

  • A text from your number for each new row, a few minutes after it appears (or at once, when a Google Form feeds the sheet).
  • Two columns on the sheet that say when each row was texted and which BlueBot text it was.
  • Rows that were already in the sheet left alone.

What you need

  • A BlueBot account with your Mac connected.
  • A Google Sheet with a header row and one row per person, including a mobile number column.
  • People in the sheet who asked to hear from you by text. A sign-up form should say so beside the number.

Luis’s sheet has these columns. Add the last two to yours, empty:

ColumnHeaderWhat’s in it
ATimestampWhen the form was sent. Google Forms fills it.
BFirst nameThe parent’s first name.
CMobileTheir mobile number.
DClassThe class they picked.
ETexted atEmpty until the row is texted.
FBlueBot idThe text’s msg_ id, for finding it later.

There are three ways to build it. Zapier and Make take a few minutes and no code. Apps Script runs inside Google with no third party at all, and is the way to go when you don’t use Zapier or Make already.

With Zapier

Start the Zap with the sheet

In Zapier, make a Zap. For the trigger, pick Google Sheets and the New Spreadsheet Row event, sign in to Google, then pick the spreadsheet and the Sign-ups worksheet. Press Test trigger to load a row.

Add BlueBot’s Send Text action

Add an action step, pick BlueBot and the Send Text action. Connect your account with an API key from Developers, then API keys, in the BlueBot dashboard (the messages:send scope is enough). Then fill in:

BlueBot fieldMap it to
ToThe Mobile column from the trigger.
TextHi, the First name column, then , this is Luis at Northside Tutoring. You’re signed up for, the Class column, . Reply here with any questions.
Metadatasource set to google-sheets, and row set to the trigger’s Row ID.
ServiceLeave it on Auto: iMessage when the parent uses it, otherwise SMS through your iPhone.

Write back to the row

Add a second action: Google Sheets, Update Spreadsheet Row. Pick the same spreadsheet and worksheet, set Row to the trigger’s Row ID, then set Texted at to the Send Text step’s Created At and BlueBot id to its ID.

Turn it on

Turn the Zap on. Zapier looks for new rows on its schedule (every one to fifteen minutes, by plan) and sees rows added at the bottom of the sheet only.

With Make

Watch the sheet

Make a scenario. Add Google Sheets, Watch New Rows, pick the spreadsheet and the Sign-ups sheet, say the table has headers, and choose to start from rows added from now on, so the parents already in the sheet aren’t texted.

Send the text

Add BlueBot, Send Text. Connect it with an API key. Map To to the Mobile column and write Text with the First name and Class columns mapped in, as in the Zapier step above. Set Metadata source to google-sheets and row to the row number Make gives.

Write back

Add Google Sheets, Update a Row, with the row number from the first module, and map Texted at and BlueBot id from the Send Text module’s output.

Schedule it

Set the scenario to run every few minutes and turn it on.

With Apps Script

Apps Script runs inside your Google account and can call BlueBot itself. The script below looks through the sheet, texts each row that has a number and no Texted at, and writes back when it went. A Google Form can start it the moment a parent presses Submit; a timer catches rows typed in by hand.

Mark the rows already in the sheet

Before anything runs, type before BlueBot in the Texted at cell of every row that’s already there. The script skips any row with something in that cell, so the parents who signed up last month won’t get a text today.

Add the script

In the sheet, choose Extensions, then Apps Script. Replace what’s in the editor with this script and save it:

Code.gs
/**
 * Texts each new sign-up from your own number through BlueBot, then writes when it went and the text's id back to
 * the row. Columns: A Timestamp, B First name, C Mobile, D Class, E Texted at, F BlueBot id.
 */
const BLUEBOT_URL = 'https://usebluebot.com/api/v1/messages';
const SHEET_NAME = 'Sign-ups';

function textNewSignUps() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(1000)) return; // the last run is still going; this row will be there next time
  try {
    const apiKey = PropertiesService.getScriptProperties().getProperty('BLUEBOT_API_KEY');
    const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
    const rows = sheet.getDataRange().getValues();

    for (let i = 1; i < rows.length; i++) {
      const [timestamp, firstName, mobile, className, textedAt] = rows[i];
      if (!mobile || textedAt) continue;
      const rowNumber = i + 1;

      // One key per sign-up: if a run sends and stops before writing back, the next run gets the same text back
      // instead of sending a second one.
      const stamp = timestamp instanceof Date ? timestamp.getTime() : rowNumber;
      const idempotencyKey = ['sheets', sheet.getSheetId(), stamp, String(mobile).replace(/\D/g, '')].join('-');

      const response = UrlFetchApp.fetch(BLUEBOT_URL, {
        method: 'post',
        contentType: 'application/json',
        headers: { Authorization: 'Bearer ' + apiKey, 'Idempotency-Key': idempotencyKey },
        payload: JSON.stringify({
          to: String(mobile),
          text: 'Hi ' + firstName + ', this is Luis at Northside Tutoring. You’re signed up for ' + className + '. Reply here with any questions.',
          metadata: { source: 'google-sheets', row: String(rowNumber) },
        }),
        muteHttpExceptions: true,
      });

      const answer = JSON.parse(response.getContentText());
      if (response.getResponseCode() === 201) {
        sheet.getRange(rowNumber, 5, 1, 2).setValues([[new Date(), answer.id]]);
      } else if (answer.error.code === 'rate_limited') {
        return; // try the rest on the next run
      } else {
        // Leave the row for a person: the reason is in the sheet, and the row is skipped from now on.
        sheet.getRange(rowNumber, 5).setValue('Not sent: ' + answer.error.message);
      }
    }
  } finally {
    lock.releaseLock();
  }
}

Give it your API key

In the Apps Script editor, open Project Settings (the gear), scroll to Script Properties, press Add script property, and add BLUEBOT_API_KEY with a key from BlueBot’s Developers, then API keys. The key stays in the script’s settings, out of the code and out of the sheet, where anyone the sheet is shared with could read it.

Run it once by hand

Pick textNewSignUps at the top of the editor and press Run. Google asks you to allow the script to read the sheet and call an outside service; allow it. Google shows a warning because you wrote the app yourself; choose Advanced and go on to your project.

Run it on its own

Open Triggers (the clock) and press Add Trigger:

  • To text as soon as a Google Form is sent, choose the function textNewSignUps, event source From spreadsheet and event type On form submit.
  • For rows typed in by hand, add a second trigger: event source Time-driven, Minutes timer, Every 5 minutes.

The two can run side by side. The lock keeps them from texting the same row at once, and the key keeps a row from being texted twice even if one run stops halfway.

Each run makes this request for every new row:

POST/v1/messagesSend a text →
curl -X POST "https://usebluebot.com/api/v1/messages" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sheets-0-1762890000000-15555550123" \
  -d '{
    "to": "+15555550123",
    "text": "Hi Maria, this is Luis at Northside Tutoring. You’re signed up for Algebra I on Tuesdays. Reply here with any questions.",
    "metadata": {
      "source": "google-sheets",
      "row": "14"
    }
  }'

Test it

Add a row at the bottom of the sheet with your own first name and your own mobile number (or send your own Google Form), then wait for the trigger or press Run.

You gotIt meansDo next
A text on your phone, and a time and a msg_ id in your rowIt works.Delete your test row, or leave it: it won’t be texted again.
Not sent: … in the Texted at cellBlueBot refused the text, and the message says why: an empty or unreadable number, or a key without messages:send.Fix the row, clear the cell, and the next run tries again.
A time and an id in the row, but no text yetBlueBot accepted the text and it’s waiting: quiet hours, your Mac asleep, or its turn in the pacing.Look the id up in Messages in the dashboard; its hold says why and until when.
Nothing in the row after the trigger ranThe run didn’t reach your row, or didn’t run.In Apps Script, Executions lists every run with its log and any error. In Zapier, the Zap’s history says why it stopped.
Not sent: The request carried no key…The script property is missing or misspelled.Check BLUEBOT_API_KEY in Project Settings.

When it goes wrong

  • Every row in the sheet got a text. The rows that were there before weren’t marked. BlueBot paces texts and stops at your daily limit for new people, so check Messages at once and cancel what’s still queued there. Then mark the old rows as in the first step.
  • Rows sorted or inserted in the middle. Zapier and Make watch for rows added at the bottom; a row inserted in the middle or a sheet sorted after the fact can be missed or seen again. Apps Script reads the Texted at column, so it doesn’t care where a row is.
  • A long list arrives at once. Pasting 300 rows is a broadcast, not a reply. Your sending rules space texts 20 seconds apart and hold new people over your daily limit, so the last ones go hours later or tomorrow. For a one-off list, use Broadcasts in the dashboard, which shows when the last text will go before you send.
  • Somebody replied STOP. They’re on your stop list, and BlueBot cancels any text to them from now on. The script writes the reason into the row the next time it tries.

Next