03 / Project · JavaScript · Google Apps Script

ServiceNow → Google Chat Alert Bot

A serverless bridge that monitors a shared Gmail inbox for ServiceNow incident emails, parses the relevant fields with regex, and pushes interactive Google Chat V2 Cards via webhook. Event-driven, zero infrastructure, running unattended 24/7.

Google Apps Script JavaScript Gmail API Chat Webhooks V2 Cards Serverless
ServiceNow Incident alert email Gmail Shared team inbox Apps Script parse · route · format Google Chat V2 Card via webhook time-based trigger every 1 minute source transport logic destination
At a glance
1 min
Poll interval
$0
Infrastructure cost
24/7
Autonomous operation
~150
Lines of code

Successfully implemented and deployed at one of my employers — running quietly in the background of the IT support team's shared inbox, routing incidents into the channel where they're actually read.

Why it exists

ServiceNow alerts that nobody read.

The IT support team lived in Google Chat. ServiceNow sent incident notifications by email, into a shared Gmail inbox that nobody actively watched. High-priority incidents were sitting unread for hours because email had quietly become a write-only medium.

The team had Google Workspace, no budget for a paid integration platform, and no appetite for a piece of infrastructure to babysit. The right answer was something serverless, free, and trivially auditable.

The constraint: no servers, no paid SaaS, no credentials sitting anywhere outside Google Workspace.

How it works

A time-triggered Apps Script with regex glue.

01

Time-based trigger

Apps Script fires the handler once a minute. Cheap, native, no external scheduler. The function is idempotent — re-running on the same thread is a no-op.

02

Gmail search for unread alerts

A scoped Gmail query (from:servicenow is:unread label:incidents) returns only the threads that haven't been processed yet, keeping the script's work bounded.

03

Regex extraction

Three regexes pull the INC number, priority, and affected user out of the alert body. ServiceNow's email template is stable enough that this beats a heavier HTML parser.

04

V2 Card to webhook

Builds a Google Chat V2 Card with header, key/value rows, priority chip, and an "Open in ServiceNow" button, then POSTs it to the room's incoming webhook URL.

05

Mark-read on success

Only after a 200 OK from the webhook does the thread get marked read and labelled — so a transient network blip results in a retry, not a dropped alert.

06

Error reporting in-channel

A separate try/catch posts parse failures as a muted card into a side channel for the maintainer, with the offending message ID. No silent breakage.

Code shape

The whole core, in < 30 lines.

// runs every minute via a time-based trigger function processIncidents() { const threads = GmailApp.search( 'from:servicenow is:unread label:incidents' ); threads.forEach(thread => { const msg = thread.getMessages()[0]; const body = msg.getPlainBody(); const incident = { number: body.match(/INC\d{7,}/)?.[0], priority: body.match(/Priority:\s*(.+)/)?.[1], user: body.match(/Caller:\s*(.+)/)?.[1], url: body.match(/https:\/\/\S+\/incident\?\S+/)?.[0] }; const res = UrlFetchApp.fetch(WEBHOOK_URL, { method: 'post', contentType: 'application/json', payload: JSON.stringify(buildCardV2(incident)) }); if (res.getResponseCode() === 200) { thread.markRead(); thread.addLabel(PROCESSED); } }); }
What the team sees

A V2 Card in the right channel, ~30 seconds after the alert.

ServiceNow Bot · just now
New incident — INC0098214
Posted automatically · servicenow → gchat
Priority
P1 — High
Caller
j.kowalski@example.com
Service
VPN Access
Opened
2026-05-19 09:14
Spec

Footprint & surface.

RuntimeGoogle Apps Script (V8)
TriggerTime-driven, every 1 minute
InputsGmail search query, regex set, webhook URL
OutputsGoogle Chat V2 Card via incoming webhook
SecretsWebhook URL stored in Script Properties
InfrastructureNone — fully managed by Google Workspace
Cost$0 — within Workspace quota
Failure modeRetry on next minute; parse errors reported in-channel