SMSTunnel for Google Sheets
Texts from your spreadsheet, from your own phone and number
Your spreadsheet writes, your phone sends
Your spreadsheet hands each text to SMSTunnel, and your own Android phone sends it from its own number. People see a number they know, and they can reply to you. There is no server to run: everything is Google Apps Script inside your own spreadsheet.
Two ready templates
Status texts
When a row's status becomes "Shipped" or "Ready", the number in that row gets your text, with the row's details filled in. Once per row.
Hi Ana, your order A-100 has shipped.
Appointment reminders
Each appointment in your sheet gets one reminder text a few hours before, between the hours you allow.
Hi {Name}, a reminder of your appointment on {Date} at {Time}. Reply to this text if you need to change it.
Status texts
-
Choose the sheet, the status column, the value (like
Shipped) and the phone column, and write the text with the row's columns as placeholders:Hi {Name}, your order {Order} has shipped. - When someone changes a status to that value, the number in that row gets the text, and Texted at (a column SMSTunnel adds) shows when. A note on that cell shows the number and the message id. A row is never texted twice; to text it again, clear its "Texted at" cell and set the status again.
- Rows that already have the value when you set it up are marked as skipped, so only changes from now on send texts.
- Pasting the value into many rows sends at most 20 texts at once (you choose the limit, up to 50); the rest wait.
- Edits made by other apps or scripts don't count as edits for Google, so they don't run the edit trigger. Tick Also check every 15 minutes to cover those, or use Send waiting texts now.
- Menu: Set up, Send a test text, Send waiting texts now, Turn texts off, Help.
Appointment reminders
-
Choose the sheet, the phone column, the date column and, if the time is in its own column, the time column. Dates
can be real dates or text like
2026-11-02 14:30; times like14:30or2:30 PM. They are read in the spreadsheet's time zone (File → Settings). - Every hour (or once a day at the hour you choose), each row whose appointment is within the next hours you choose (24 by default) gets one reminder, soonest first, and Reminder sent records it.
- Texts go out only between the hours you choose (8:00 to 21:00 by default), at most 25 per run (up to 100); the rest go out on the next run.
- Rows without a valid number are marked Not sent with the reason. Preview the next reminders shows what would go out, without sending anything.
- Menu: Set up, Send a test text, Preview the next reminders, Send due reminders now, Turn reminders off, Help.
1. Get your key
- 1. Install the SMSTunnel app from Google Play on your Android phone and sign in.
- 2. In your dashboard, open Apps → Add an app, pick Google Sheets, Forms or Calendar, name it (for example "Google Sheets") and click Create the key.
- 3. Copy the key right away: it's shown only once. You can stop or delete the app there anytime, and Google Sheets can't send texts anymore.
Get the steps with your key in them
It opens Add an app with Google Sheets, Forms or Calendar picked. Once the key is made, the dashboard shows every step and command with the key already filled in, ready to copy.
Make one key per spreadsheet or form, so you can stop one without touching the others.
2. Add the template to your spreadsheet
Ready-made copies (Make a copy) are coming soon. Pasting the code works today and takes a few minutes:
- 1. Open your spreadsheet, then Extensions → Apps Script.
- 2. Make the same files as the template's folder on GitHub
(the one you want: apps-script/sheets-status-texts or apps-script/sheets-reminders)
and paste each one's content:
-
SmsTunnel,SmsTunnelKitandCode: script files (+ → Script; the editor names them.gs); Setup: an HTML file (+ → HTML).
-
- 3. Open Project Settings (the gear), tick
Show "appsscript.json" manifest file in editor, open
appsscript.jsonand replace it with the template's. - 4. Save, go back to your spreadsheet and reload it. The SMSTunnel menu shows up after a few seconds (reload the page once if it doesn't).
3. Set it up
- 1. Choose SMSTunnel → Set up. Google asks you to allow the script the first time (see below).
- 2. Paste your key, click Check, choose the phone that sends the texts, fill in the rest and click Save and turn on.
- 3. Send yourself a test: SMSTunnel → Send a test text.
Google's permission screen, explained
The first time you choose Set up, Google asks you to allow the script. Because it is your own copy of a script Google hasn't reviewed, Google shows "Google hasn't verified this app". That is expected for a script you own: click Advanced → Go to … (unsafe). You can read every line of it in Extensions → Apps Script first. Google may let you tick the permissions one by one: tick all of them, because the texts can't go out without them (Set up asks again if one is missing).
What each permission is for (Google's screen words them its own way):
- Connect to an external service
- Handing each text to SMSTunnel at https://smstunnel.io. Nothing else is called. (script.external_request)
- This spreadsheet only
- Reading your rows and writing "Texted at", "Reminder sent" and the "SMSTunnel log" sheet. No other spreadsheet. (spreadsheets.currentonly)
- Show dialogs
- The SMSTunnel menu, the setup dialog and the previews. (script.container.ui)
- Run when you are not present
- The triggers that run when a response comes in, a cell changes or the time comes. (script.scriptapp)
You can remove the script's access anytime in your Google Account (Security → Third-party apps & services), and stop every text at once by deleting the key under Apps in your SMSTunnel dashboard.
If it doesn't work
- No SMSTunnel menu
- Reload the spreadsheet and wait a few seconds.
- "Google hasn't verified this app"
- Expected for your own copy: Advanced → Go to … (unsafe). See the permission screen above.
- A text didn't go out
- Help shows the last problem; the SMSTunnel log sheet shows each text and why one was not sent. Check that your phone is online under Phones in your dashboard: when it's offline, texts wait and go out when it's back.
- "has no country code"
- Write the number with + and the country code, or set the country code in Set up → More settings.
- Google emails "Summary of failures for Google Apps Script"
- A run stopped on purpose, usually because the key was deleted or the chosen phone was unlinked. Open Help, then Set up to fix it.
- A row was not texted when its status changed
- Its "Texted at" cell is not empty (a row is texted once), or another app changed it: turn on Also check every 15 minutes, or use Send waiting texts now.
- A row shows Sending…
- A try that may not have gone through (the server was busy, or the key stopped working). The next change or run sends it with the same key, so it never goes out twice.
- Texts arrive at odd times
- Check the spreadsheet's time zone (File → Settings).
Good to know
- Numbers: write them in international format, like
+14155550123. Tip: Format → Number → Plain text keeps the+in a cell. Numbers without a country code need the country code in Set up; without it they are skipped, so a text never goes to a number in the wrong country. - The log: every text, and every row that could not be texted, is listed in the SMSTunnel log sheet (the last 2000 lines).
- When something stops the texts (a deleted key, a phone no longer linked), the run fails on purpose, so Google emails you about it, and Help shows the last problem. Short outages and "too many texts" just wait for the next run.
- Your phone offline: the text waits and goes out as soon as the phone is back online.
- One setup per file: if two editors both run Set up, the latest one sends and the other one's trigger removes itself, so nobody gets a text twice.
- Turn texts off removes the triggers; your settings and key stay, so Set up turns them back on.
- Limits: SMSTunnel takes 30 send requests a minute, up to 1600 characters per text, and your plan's daily allowance applies. Google Apps Script (quotas) gives a free Google account 20,000 URL Fetch calls a day (one per text), 90 minutes of trigger run time a day and 6 minutes per run; Google Workspace accounts get 100,000 calls and 6 hours. The templates pause 2 seconds between texts and cap each run, so even a run at the highest cap (100 texts) ends within Google's 6 minutes.
Privacy and security
- The key stays in the script's user properties: only you can read it, and it is never written to a cell, a note or a log. The setup dialog shows only its last 4 characters.
- Who can change the script: everyone with edit access to the spreadsheet can edit its script, and a trigger runs with the permissions of the person who set it up. Give edit access only to people you trust.
- What leaves your file: for each text, the number and the text go to SMSTunnel and to your phone. Logs stay in your spreadsheet. Nothing is sent anywhere else.
- Safe writes: anything SMSTunnel writes to a cell (a reason from the server, for example) is written as text, never as a formula.
- Stop everything by deleting the key under Apps in your SMSTunnel dashboard.
Questions
- Can it text everyone in my sheet at once?
- No, and on purpose. Each template texts only the numbers it was set up for: the row whose status changed, the appointment coming up. One text per number, with a limit per run. Send texts only to people who gave you their number for them.
- Will it text a number twice?
- No. Every template writes down what it sent ("Texted at", "Reminder sent") and checks it before each text.
- Does it cost anything?
- The templates are free, and Google Apps Script is free with any Google account. The texts count against your SMSTunnel plan, and your mobile plan applies to the texts your phone sends.
- Whose number do people see?
- Yours. The texts leave your own phone, from its SIM, like a text you type yourself. Replies come back to your phone.
- Where is my key kept?
- In the script's own private storage for your account, which only you can read. It never goes into a cell or a log. Delete the key under Apps in your dashboard and every text stops at once.
- Does it work with Google Workspace accounts?
- Yes, with personal Google accounts and Google Workspace accounts. Workspace accounts get higher daily limits from Google.
Your own Apps Script code
For developers: paste apps-script/library/SmsTunnel.js
into your project and call SmsTunnel.sendSms(...). Save the key once, without writing it into
your code:
function saveSmsTunnelKey() {
var ui = SpreadsheetApp.getUi();
var answer = ui.prompt('SMSTunnel key', 'Paste the key from Apps → Add an app in your SMSTunnel dashboard.', ui.ButtonSet.OK_CANCEL);
if (answer.getSelectedButton() === ui.Button.OK) SmsTunnel.setApiKey(answer.getResponseText());
} setApiKey keeps it in your project's user properties, which only you can read. The library
reads the key from options.apiKey, then user properties, then script properties.
// Text the number in the row you are on.
function textThisRow() {
var sheet = SpreadsheetApp.getActiveSheet();
var row = sheet.getActiveRange().getRow();
var phone = SmsTunnel.normalizePhone(sheet.getRange(row, 2).getDisplayValue(), '+1');
if (!phone.ok) throw new Error(phone.message);
var result = SmsTunnel.sendSms(phone.number, 'Your order is ready for pickup.');
sheet.getRange(row, 5).setValue(result.queued ? 'Waiting for my phone' : 'Texted');
} - sendSms(to, message, options)
- One text to one number. options: deviceId (one of your phones), defaultCountryCode (like "+1", for numbers without + or 00), idempotencyKey (a name for this one text, like "order-17-shipped": sent again with the same key within 24 hours, SMSTunnel answers with the first result instead of a second text), apiKey. Returns { messageId, queued, branded, remaining }; queued is true when your phone was offline (the text waits).
- sendCode(to, message, options)
- A sign-in code, through the separate allowance for codes. The text must hold a code of 4 to 10 digits standing on its own (482913, 12-345678) or 123-456, at most 320 characters; otherwise nothing is sent (NO_CODE).
- getStatus(messageId)
- pending, sent, delivered or failed, with times.
- getPhones()
- Your phones: id, name, isOnline, lastSeenAt.
- getUsage()
- Today's usage, your plan's limits and what's left.
- setApiKey(key, where), clearApiKey(where), hasApiKey(), apiKeyHint()
- The saved key (where: 'user', the default, or 'script'). The hint is the last 4 characters.
- normalizePhone(value, countryCode)
- The exact number a text would go to ({ ok, number }), or why not ({ ok: false, reason, message }), read the way SMSTunnel reads numbers.
- containsCode(text), cleanCountryCode(value), isRetryable(error)
- The same checks SMSTunnel makes.
One number per call: to text several people, call sendSms once for each of them. Every
function throws an Error named SmsTunnelError when a text can't be
handed over; its code, status and retryable
say why:
- INVALID_NUMBER, INVALID_ARGUMENT, MESSAGE_TOO_LONG, NO_CODE, NO_API_KEY
- Refused before anything was sent. (status 0, not retryable)
- BAD_REQUEST
- SMSTunnel refused a field, such as a number it can't read. (400, not retryable)
- UNAUTHORIZED
- The key is wrong or was deleted. (401, not retryable)
- PERMISSION_REQUIRED or FORBIDDEN
- The key lacks a permission (error.permission says which), or your plan doesn't include it. (403, not retryable)
- NOT_FOUND
- No such message or phone for this key. (404, not retryable)
- RATE_LIMITED
- Too many texts in a short time; error.retryAfter gives the seconds to wait. Without retryAfter, the plan's limit for the day or month is reached. (429, retryable)
- IDEMPOTENCY_KEY_IN_PROGRESS
- The first request with this idempotencyKey is still running. (409, retryable)
- IDEMPOTENCY_KEY_REUSED
- This idempotencyKey was used in the last 24 hours for a different text: use a new key for a new text. (422, not retryable)
- SERVER_ERROR, NETWORK_ERROR
- SMSTunnel couldn't be reached; try again a little later. (5xx or 0, retryable)
- REDIRECT, INVALID_RESPONSE, HTTP_ERROR
- Something between you and SMSTunnel answered instead. (other, not retryable)
Build it with an AI assistant
Copy this prompt into Claude, ChatGPT, Cursor or Copilot and replace the part in [brackets]. Don't paste your API key into the chat: the prompt doesn't need it.
Help me write Google Apps Script code (V8 runtime) that sends text messages from my Google Sheets spreadsheet with SMSTunnel.
About SMSTunnel (https://smstunnel.io): my own apps and tools hand text messages (SMS) to my SMSTunnel account, and my own Android phone sends them from my own number, on my own SIM and plan. The phone runs the SMSTunnel app from Google Play (https://play.google.com/store/apps/details?id=io.smstunnel.app), signed in to my account.
What I want: [describe what should send a text, to whom, and when]
How it works:
- Install: paste the file SmsTunnel.js from "SMSTunnel for Google Sheets, Forms and Calendar" (https://github.com/narcisbodea/smstunnel-google, apps-script/library/SmsTunnel.js) into my Apps Script project. Then call SmsTunnel.sendSms(to, message, options).
- SmsTunnel.sendSms(to, message, { deviceId, defaultCountryCode }) makes one call to POST https://smstunnel.io/api/v1/sms/send with the header X-API-Key: <key> and the JSON body { "to": "+14155550123", "message": "...", "deviceId": "...", "defaultCountryCode": "+1" } (deviceId and defaultCountryCode are optional). It returns { messageId, queued }.
- One recipient per call: to text several people, call it once per person.
- Numbers in international format (+14155550123). defaultCountryCode ("+1", "40") completes numbers written without + or 00, and wins over the phone's own country. SmsTunnel.normalizePhone(value, countryCode) gives the exact number or the reason it is refused.
- Texts: up to 1600 characters. Sending is limited to 30 requests a minute.
- Sign-in codes: SmsTunnel.sendCode(to, message) calls POST /api/v1/sms/send-2fa; the text must contain a code of 4 to 10 digits standing on its own (or 123-456), at most 320 characters, or it is refused (400).
- Errors throw an Error named SmsTunnelError with status, code and retryable: 401 = wrong or deleted key, 403 = the key lacks a permission (code PERMISSION_REQUIRED) or the plan doesn't allow it, 429 = too many requests (retryable, retryAfter in seconds), 5xx and network errors = retryable.
- When my phone is offline the text waits (queued: true) and goes out as soon as the phone is back online.
- Optional { idempotencyKey: "..." } (the Idempotency-Key header): a retry with the same key within 24 hours gets the first answer instead of sending a second text. Use one key per text you mean to send.
- The API key comes from my SMSTunnel dashboard (https://dashboard.smstunnel.io): Apps → Add an app → Create the key. It is shown only once and can send texts and check whether they went out.
Rules for your answer:
- Keep the API key in an environment variable or the platform's secret store (for example SMSTUNNEL_API_KEY). Never put it in source code, in a repository, or in code that runs in a browser or a mobile app: call SMSTunnel from a server. I will not paste my key in this chat; use a placeholder.
- Validate phone numbers before sending (international format, like +14155550123) and skip or report the invalid ones.
- Handle errors: log or show what went wrong, retry only after 429, 5xx or network errors and only a few times, and never send the same text twice.
- Only send texts people expect from me: alerts to myself, reminders, sign-in codes, updates they asked for. No unsolicited texts.
- Keep the API key in PropertiesService (SmsTunnel.setApiKey from a one-time prompt or dialog, which stores it in user properties). Never put the key in the code, in a cell, in a log, or in HTML/JavaScript that runs in the browser.
- Check every number with SmsTunnel.normalizePhone before sending; skip invalid ones and say why.
- Handle errors: stop and tell me on 401 or 403; leave the item for the next run on 429 or 5xx; write down what was sent so nothing is sent twice; cap the texts per run.
- Only send texts people expect from me: to people who gave me their number for these texts.
- Apps Script only: UrlFetchApp (no fetch), no setTimeout, no async/await; installable triggers for automation. Responsible use
Built for alerts to yourself and your team, and texts people expect from you. One text goes to one number. Sending unsolicited messages from a consumer SIM generally breaches mobile carrier terms of service.
Google, Google Sheets, Google Forms, Google Calendar and Google Apps Script are trademarks of Google LLC. SMSTunnel is a third-party integration and is not affiliated with, endorsed by, or sponsored by Google.