appscript.dev
Automation Advanced Gmail

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) and MOBILE (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/smsed label 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

  1. smsCriticalAlerts looks up the alerts/critical label. If it does not exist yet, the script logs a message and stops rather than throwing.
  2. It fetches (or creates) the alerts/critical/smsed sub-label — the marker for threads that have already been texted.
  3. For every thread on the critical label, it checks whether the smsed sub-label is present and skips the thread if so. This is what makes the script safe to run every couple of minutes.
  4. 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.
  5. It applies the smsed sub-label to the thread, so the next run leaves it alone, and tallies how many alerts went out.
  6. sendSMS builds 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 subjectFrom
Payment gateway returning 502Stripe Monitoring
URGENT: site down for our teamPriya 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:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add Trigger.
  3. Choose smsCriticalAlerts, event source Time-driven, type Minutes timer, interval Every 2 minutes.
  4. 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/critical filters 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 smsed label 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 muteHttpExceptions so 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