Gmail Events — Open Source Research¶
This document summarizes how open-source projects detect new Gmail messages and why Voxpost chose its approach.
The official mechanism¶
Google does not expose a WebSocket or desktop notification API for third-party mail clients. The supported event path is:
users.watch— register a mailbox (optionally filtered by label, e.g.INBOX) with a Cloud Pub/Sub topic.- Pub/Sub notification — Gmail publishes a small payload when the mailbox changes.
users.history.list— resolve the notification into concrete changes since the last storedhistoryId.users.messages.get— fetch metadata (From, Subject, snippet) for new message IDs when needed.- Watch renewal — call
watchagain before expiration (~7 days) or notifications stop.
References:
Projects evaluated¶
| Project | License | Activity | Event path | Fit for Voxpost |
|---|---|---|---|---|
| gogcli | MIT | Active (2026) | watch → Pub/Sub push → local HTTP handler → hook URL |
Best behavioral reference — renewal, stale history recovery, label filters, fetch delay |
| gmailpush | MIT | Last updated 2023 | watch → Pub/Sub push → Express handler → getMessages() |
Good Node library; assumes public push endpoint |
| EmailEngine | Source-available (paid to run) | Active | watch → Pub/Sub → poll subscription → webhooks |
Validates production architecture; too heavy for Voxpost core |
| GmailNotify-PubSub-GCP | GPL-3.0 | 2021 | Pub/Sub → Cloud Functions → webhook | Serverless demo, not a local daemon |
| Realtime-Gmail-Listener | — | Small (2025) | Cloud Run + Apps Script | Wrong shape for on-device TTS |
| n8n Gmail Trigger | Fair-code | Active | Polls Gmail on an interval | Easy OAuth UX, not true push |
| IMAP IDLE (e.g. mail-listener) | MIT | Various | TCP IDLE on IMAP | Simpler stack, worse consumer UX (app passwords) |
What OSS converges on¶
Every serious Gmail push implementation uses watch + Pub/Sub + history.list. Polling (n8n) and IMAP IDLE are alternatives but weaker for Voxpost’s goals:
- Polling — delayed, quota-heavy, not event-driven.
- IMAP IDLE — near real-time but often requires app passwords; OAuth UX is worse for normal users.
Two delivery patterns for Pub/Sub¶
| Pattern | Used by | Pros | Cons |
|---|---|---|---|
| Push subscription → HTTP endpoint | gmailpush, gogcli watch serve |
Simple request handler | Needs reachable URL (HTTPS or tunnel) |
| Pull / streaming pull | EmailEngine, local-agent patterns | No open ports; fits desktop daemon | Long-lived subscriber process |
Voxpost uses streaming pull so the daemon runs locally without exposing a webhook.
Patterns worth copying from gogcli¶
From gogcli Gmail watch docs:
- Store only
historyId, watch expiration, topic, and labels — not mail content. - Filter to
INBOX; excludeSPAMandTRASHby default. - Restrict history types to
messageAddedfor new-mail detection. - Fetch delay (~2–3 s) after Pub/Sub notification before
history.listto avoid indexing races. - Stale
historyIdrecovery — fall back without replaying the entire mailbox as new events. - Optional hook payload:
from,subject,snippetwith body size caps when body is included.
What Voxpost rejected¶
- n8n / Zapier as the core event layer — polling, external dependency.
- EmailEngine as a dependency — full email API platform, paid runtime.
- IMAP IDLE for v1 — weaker permission story for non-technical users.
- Per-user GCP setup — normal users should only see “Connect Gmail”; the app owner configures Cloud once.
Voxpost decision¶
| Layer | Choice |
|---|---|
| Detection | Gmail API users.watch + Pub/Sub + users.history.list |
| Delivery | Pub/Sub streaming pull in a local daemon |
| User auth | OAuth 2.0 (gmail.readonly) — “Sign in with Google” |
| Operator setup | One GCP project, one topic, OAuth consent screen (app owner) |
| Persistent state | Refresh token + lastHistoryId + watch metadata only |
| Runtime | Python (see RUNTIME.md) |
See BLOCK_1_GMAIL_EVENTS.md for scope and acceptance tests.