Build an email-to-text urgent alert channel
Push critical Gmail notifications to a phone via Twilio for moments your inbox isn't enough.
Published Mar 3, 2026
Some emails cannot wait for the next time someone glances at their inbox. A server alarm, a payment failure, a client escalation — by the time it surfaces in a busy inbox, the window to act has often closed. Email is a fine medium for most things, but it is a poor alarm.
At Northwind, the messages that genuinely need an instant response all land
under one Gmail label, alerts/critical — fed by filters and forwarding rules
from monitoring tools and key clients. This script watches that label and pushes
each new thread to Awadesh’s phone as an SMS through Twilio, so a critical email
becomes a buzz in his pocket within a couple of minutes. A second label,
alerts/critical/smsed, marks what has already been sent so nothing texts
twice.
What you’ll need
- A Gmail label named
alerts/critical, with filters routing your genuinely urgent mail into it. Keep this label strict — the value of an alert channel collapses the moment it cries wolf. - A Twilio account with an SMS-capable phone number.
- Four values saved in Script Properties:
TWILIO_SID,TWILIO_TOKEN,TWILIO_FROM(your Twilio number) andMOBILE(the destination phone). See Store API keys and secrets securely for how to set these without pasting them into the code. - Nothing else — the script creates the
alerts/critical/smsedlabel itself.
The script
// The label that holds genuinely urgent mail. Keep its filters strict.
const CRITICAL_LABEL = 'alerts/critical';
// Sub-label applied once a thread has been texted, so it never texts twice.
const SMSED_LABEL = 'alerts/critical/smsed';
// Twilio caps a single SMS segment at 160 characters; trim to fit.
const SMS_MAX_LENGTH = 160;
/**
* Finds critical threads that have not yet been texted, sends each one as an
* SMS, and marks it so the next run skips it.
*/
function smsCriticalAlerts() {
const label = GmailApp.getUserLabelByName(CRITICAL_LABEL);
// 1. Bail out early if the critical label does not exist yet.
if (!label) {
Logger.log('Label "' + CRITICAL_LABEL + '" not found — nothing to do.');
return;
}
// 2. Get (or create) the "already texted" sub-label.
const sent = GmailApp.getUserLabelByName(SMSED_LABEL)
|| GmailApp.createLabel(SMSED_LABEL);
let count = 0;
for (const thread of label.getThreads()) {
// 3. Skip any thread that has already been texted.
const alreadySent = thread.getLabels()
.some((l) => l.getName() === SMSED_LABEL);
if (alreadySent) continue;
// 4. Build a short alert from the first message: subject and sender name.
const msg = thread.getMessages()[0];
const senderName = msg.getFrom().split('<')[0].trim();
sendSMS(msg.getSubject() + ' — ' + senderName);
// 5. Mark the thread so it is not texted again on the next run.
thread.addLabel(sent);
count++;
}
Logger.log('Sent ' + count + ' alert(s).');
}
/**
* Sends a single SMS through the Twilio REST API. Credentials live in Script
* Properties — they are never pasted into the code.
*/
function sendSMS(body) {
const props = PropertiesService.getScriptProperties();
const sid = props.getProperty('TWILIO_SID');
// Twilio authenticates with HTTP Basic: base64 of "sid:token".
const auth = Utilities.base64Encode(sid + ':' + props.getProperty('TWILIO_TOKEN'));
UrlFetchApp.fetch(
'https://api.twilio.com/2010-04-01/Accounts/' + sid + '/Messages.json',
{
method: 'post',
headers: { Authorization: 'Basic ' + auth },
payload: {
From: props.getProperty('TWILIO_FROM'),
To: props.getProperty('MOBILE'),
// Trim to one SMS segment so multi-part billing never sneaks in.
Body: body.slice(0, SMS_MAX_LENGTH),
},
muteHttpExceptions: true,
},
);
}
How it works
smsCriticalAlertslooks up thealerts/criticallabel. If it does not exist yet, the script logs a message and stops rather than throwing.- It fetches (or creates) the
alerts/critical/smsedsub-label — the marker for threads that have already been texted. - For every thread on the critical label, it checks whether the
smsedsub-label is present and skips the thread if so. This is what makes the script safe to run every couple of minutes. - For a new thread it reads the first message, pulls the subject and the
sender’s display name, and sends them as one SMS via
sendSMS. - It applies the
smsedsub-label to the thread, so the next run leaves it alone, and tallies how many alerts went out. sendSMSbuilds an HTTP Basic auth header from the Twilio SID and token, then posts to Twilio’s Messages endpoint. The body is trimmed to 160 characters so each alert stays a single, single-billed SMS segment.
Example run
Suppose two new emails land under alerts/critical between runs:
| Email subject | From |
|---|---|
| Payment gateway returning 502 | Stripe Monitoring |
| URGENT: site down for our team | Priya Shah |
On the next run the phone receives two texts:
Payment gateway returning 502 — Stripe Monitoring
URGENT: site down for our team — Priya Shah
Both threads pick up the alerts/critical/smsed label, so the run after that
sends nothing — the inbox is quiet until the next genuine alert arrives.
Trigger it
This automation only earns its keep if it runs constantly, so install a time-driven trigger:
- In the Apps Script editor, open Triggers (the clock icon).
- Click Add Trigger.
- Choose
smsCriticalAlerts, event source Time-driven, type Minutes timer, interval Every 2 minutes. - Save and approve the authorisation prompt.
A two-minute interval keeps alerts near-instant while staying well inside Apps Script’s daily trigger runtime quota.
Watch out for
- SMS costs money. Every text is a billed Twilio message, so the value of this
channel depends entirely on how disciplined the
alerts/criticalfilters are. One chatty filter and you will both pay for and ignore the noise. - It only reads the first message of each thread. If an urgent reply lands on
an older thread that was already texted, it will not re-alert. Route genuinely
new escalations into new threads, or remove the
smsedlabel to re-arm one. - The 160-character trim can clip long subjects. The text is a “look at your inbox now” nudge, not the full message — that is by design.
- Twilio trial accounts can only text verified numbers and prepend a trial
banner. Verify
MOBILE, or upgrade the account, before relying on it. - A failed Twilio call is swallowed by
muteHttpExceptionsso one bad send does not stall the batch. If alerts stop arriving, check the execution log and Twilio’s own message log to see where they are being dropped.
Related
Convert long email threads into a summary note
Collapse a thread's history into a Doc for handover — perfect for client transitions or vacation cover.
Updated Jun 6, 2026
Pull event RSVPs from emails into a Sheet
Parse yes/no replies to event invites and tally attendance automatically.
Updated Jun 2, 2026
Turn forwarded emails into project tasks
Forward to [email protected] and a row lands in the Projects sheet under the right client.
Updated May 30, 2026
Turn starred emails into a task list
Sync every starred thread into the Northwind Tasks sheet automatically.
Updated May 26, 2026
Alert when a label hits a backlog threshold
Warn the Northwind team in Slack when a Gmail label has more than N unread threads.
Updated Mar 31, 2026