Guide

Gmail Push Notifications (watch + Pub/Sub)

You want your app to react the instant new mail lands in a Gmail inbox, so you reach for push notifications — and discover Gmail doesn't POST to a webhook like most APIs. It publishes to a Cloud Pub/Sub topic you have to provision, with IAM grants, a 7-day expiry, and a historyId sync to figure out what actually changed. Here's how that works, and how to get the same new-mail signal as a signed webhook with one command.

Written by Prem Keshari Senior SRE

VerifiedCLI 3.1.16 · Gmail · last tested June 8, 2026

Command references used in this guide: nylas webhook create, nylas webhook server, nylas webhook verify, and nylas email list.

How do Gmail push notifications work?

Gmail push notifications use users.watch, which registers an inbox to publish change events to a Google Cloud Pub/Sub topic. When mail arrives, Gmail publishes a small message to that topic carrying the emailAddress and a historyId — a watermark, not the message itself. Your service receives it through a Cloud Pub/Sub push or pull subscription, then calls users.history.list with the previous historyId to fetch exactly what changed.

That indirection surprises people. Unlike calendar push, which POSTs to your URL directly, Gmail routes everything through Pub/Sub, so the delivery system is a second Google product you operate. The Gmail API push docs walk through the topic, the IAM grant, and the watch call in roughly that order.

Why does Gmail use Pub/Sub instead of a webhook?

Gmail uses Pub/Sub because it scales delivery to billions of mailboxes through one durable queue rather than opening an HTTP connection per inbox. The cost lands on you: you create a topic, grant gmail-api-push@system.gserviceaccount.com the Pub/Sub Publisher role on it, create a subscription, and only then call users.watch. A push subscription still needs a verified HTTPS endpoint; a pull subscription needs a worker polling the queue.

So the “just notify me of new mail” task becomes four pieces of Google Cloud plumbing: topic, IAM binding, subscription, and watch. Each is straightforward, but together they're standing infrastructure to operate and monitor. For a single integration that only needs to know when mail arrives, that's a lot of surface.

# Provision Pub/Sub before users.watch even runs (gcloud)
gcloud pubsub topics create gmail-push
gcloud pubsub topics add-iam-policy-binding gmail-push \
  --member="serviceAccount:gmail-api-push@system.gserviceaccount.com" \
  --role="roles/pubsub.publisher"
gcloud pubsub subscriptions create gmail-push-sub --topic=gmail-push
# ...then call users.watch with topicName, and renew it weekly.

What breaks Gmail watch?

The most common failure is silent expiry. A Gmail watch lasts at most 7 days; if your renewal job misses a cycle, notifications simply stop with no error, and your app goes quiet. Google recommends re-calling users.watch at least daily to be safe. A missing or wrong IAM binding on the topic is the second: without the Publisher role, Gmail can't publish and nothing arrives.

The third is history gaps. The historyId is valid only for a limited window; if too much time passes between syncs, users.history.list returns a 404 and you must do a full re-sync to recover. Handling that fallback correctly is its own logic. None of these are conceptual puzzles, but each is steady operational weight for a new-mail signal.

How do you get Gmail webhooks with the CLI?

The nylas webhook create command subscribes to new Gmail messages with one call — pass a callback URL and the message.created trigger. Nylas manages the underlying provider channel and renewal, and delivers each new message directly to your HTTPS endpoint with data in the body, so there's no Pub/Sub topic, no IAM grant, and no historyId reconciliation. Webhook management requires an API key (admin access).

Every delivery is signed: Nylas sets an x-nylas-signature header with an HMAC-SHA256 of the raw body keyed by your webhook secret, a cryptographic check that replaces the Pub/Sub trust model. After a notification fires, nylas email list reads the current inbox state if you need full message context.

# Subscribe to new Gmail messages — one call, no Pub/Sub (needs an API key)
nylas webhook create \
  --url https://your-app.example.com/webhooks/gmail \
  --triggers message.created \
  --description "New Gmail message notifications"

# Read the inbox after a notification arrives
nylas email list --json --limit 10

How do you test Gmail webhooks locally?

The nylas webhook server command runs a local receiver and, with --tunnel, exposes it over a cloudflared tunnel so real notifications reach your laptop — no public endpoint to deploy. Pass --secret and the server verifies the HMAC signature on each event before printing it, the same check your production handler must perform on untrusted input.

For a single captured payload, nylas webhook verify confirms a body against its signature offline, so you can unit-test the handler with a fixture. Verify before trusting any field. Native Gmail push is the right choice when you're already invested in Google Cloud or need Gmail-specific history semantics; for most apps that just need to react to new mail, the signed-webhook path removes the Pub/Sub stack, the renewal job, and the history-gap recovery.

# Receive real notifications locally over a tunnel, signatures verified
nylas webhook server --tunnel --secret "$NYLAS_WEBHOOK_SECRET"

# Verify a captured payload offline against its signature
nylas webhook verify \
  --payload-file event.json \
  --secret "$NYLAS_WEBHOOK_SECRET" \
  --signature "$SIG_FROM_HEADER"

See the Google Calendar push guide for the calendar counterpart (which uses HTTP channels, not Pub/Sub) and the webhook events reference for every trigger type.

Next steps