gmail-trigger ยท git:20260710.25b73db ยท 2026-07-10 ยท sha256 dd53529d2e44f018
gmail-trigger git:20260710.25b73dbA
Immutable. This exact content is served forever at /api/v1/blob/dd53529d2e44f018.
---
name: gmail-trigger
description: "Beta. Act on new Gmail as it arrives: get pinged only for urgent mail, digest newsletters, forward invoices โ any standing instruction, across one or several accounts."
compatibility: "Designed for Vellum personal assistants"
metadata:
emoji: "๐ฌ"
vellum:
category: "email"
display-name: "Gmail Triggers (Beta)"
includes: ["schedule"]
activation-hints:
- "User wants hands-off monitoring of their Gmail inbox"
- "Ping me / tell me when a new email arrives"
- "Watch my inbox for new messages"
avoid-when:
- "Recurring inbox cleanup, archiving, triage, or reply drafting โ use inbox-management"
- "A one-time inbox summary or search, or reading a specific email"
---
# Gmail Triggers (Beta)
Polls the user's Gmail inbox on a cron and escalates to the assistant **only when a new message arrives** โ an empty poll spends zero LLM tokens. Installing it means creating a script-mode schedule that runs the schedule's own copy of the shipped poll script. Schedule mechanics (script mode, the `schedules/<id>/` convention, waking the agent loop) are documented in the included `schedule` skill.
## Why this instead of a watcher
A watcher could only be configured through its prompt and its cadence. This
one is a script that belongs to the schedule, so the assistant can change
anything about how it behaves by simply editing the script. And because it
runs as a schedule, its runs, errors, and cost all show up on the Schedules
page in the app.
## Setup
### 1. Ensure Google is connected
The trigger reads Gmail through the user's Google OAuth connection with Gmail read access. Check with:
```bash
assistant oauth status google
```
If no connection is found, load the `vellum-oauth-integrations` skill โ it evaluates whether managed or your-own mode is appropriate and guides the user through connecting. Managed (proxy) and your-own OAuth both work โ `poll.ts` calls Gmail via `assistant oauth request`, which resolves either automatically.
### 2. Choose the accounts to watch
If `oauth status` shows **more than one** active Google connection, ask the user which inboxes to watch and pass one `--account <email>` flag per chosen inbox. With a single connection the flag can be omitted. One schedule watches all chosen accounts and delivers one combined digest.
### 3. Collect the action prompt
Ask the user what should happen when new email arrives โ e.g. "notify me only about emails needing a reply" or "summarize newsletters, flag anything from my boss". If the user doesn't care, omit the flag and the default applies: summarize what's new and flag anything urgent.
### 4. Ask about the first sync
By default the trigger starts from now and never escalates pre-existing email. Ask the user whether the first sync should instead include recent mail; if yes, append `--lookback <duration>` (`90m`/`4h`/`2d`/`1w`).
### 5. Create the schedule
Create a recurring **script-mode** schedule (default cadence every 15 minutes unless the user asks for a different one) whose command runs the schedule's own copy of the poll script with the flags chosen above:
```
bun "$VELLUM_WORKSPACE_DIR/schedules/$__SCHEDULE_ID/poll.ts" --account you@example.com --action-prompt 'Summarize new email; flag anything urgent'
```
Pass `timeout_ms: 900000` โ the poll's runtime includes the woken assistant turn. Single-quote the action prompt (the command runs through `sh`). All configuration lives in this command string, so it is visible in the schedule and editable later with `assistant schedules update <id> --script "..."`.
### 6. Copy the poll script into the schedule's directory
Read the schedule id from the create result, then:
```bash
mkdir -p "$VELLUM_WORKSPACE_DIR/schedules/<id>"
cp "$VELLUM_WORKSPACE_DIR/skills/gmail-trigger/scripts/poll.ts" "$VELLUM_WORKSPACE_DIR/schedules/<id>/poll.ts"
```
The schedule owns this copy โ customizations made to it are never touched by skill upgrades. `poll.ts` self-provisions its state on first run; create nothing else.
### 7. Verify
```bash
assistant schedules execute <id>
assistant schedules runs <id> --limit 1
```
The first run records `{"ok":true,"new":0,"accounts":[{"account":"you@example.com","baselined":true,...}]}`; later empty polls record `"new":0` without `baselined`.
## How it works
- **Deterministic poll, LLM only on new mail.** `poll.ts` syncs incrementally with Gmail's History API via `assistant oauth request --provider google` (no raw token in the script). Each mailbox's watermark is a Gmail `historyId`, advanced only past history records actually processed, so a truncated poll resumes where it left off. No model call on an empty poll.
- **Per-mailbox state.** Watermarks and dedup are keyed by the email address Gmail reports for the connection, in SQLite under `schedules/<id>/state/`. Accounts baseline, advance, fail, and recover independently โ one broken connection doesn't stop the others, and an account switch behind the schedule starts cleanly instead of misreading another mailbox's watermark.
- **At-most-once escalation.** Each account's watermark and reported-message ledger commit _before_ the digest is escalated, so a retried or restarted run never escalates the same message twice; a failed wake surfaces as a failed run instead of a duplicate digest. The ledger is script-local bookkeeping โ nothing is written to Gmail, and read/unread state is untouched.
- **Expiry recovery.** Gmail keeps history for roughly a week. If a stored `historyId` has expired, that account re-baselines and catches up with a one-day inbox search; the ledger absorbs the overlap.
- **Fenced escalation.** New mail wakes a fresh conversation with the digest passed via `--external-content` (fenced as untrusted data, never instructions); the user's action prompt goes in `--hint` as the trusted framing. The digest carries full metadata for the 50 newest messages across all accounts (sorted by `internalDate`) plus per-account totals.
- **Self-contained.** Built-ins + the `assistant` CLI only โ no dependencies.
## Managing it
- Change cadence: update the schedule's expression.
- Add or remove a watched account: edit the schedule's command string (`--account` flags).
- Customize behavior: edit the schedule's copy of `poll.ts` directly.
- Update to a newer shipped script: re-copy `poll.ts` from the skill directory into `schedules/<id>/`, re-applying any custom edits.
- Pause / resume: disable / enable the schedule.
- Remove: delete the schedule; optionally clean up its `schedules/<id>/` directory.
- If polls start failing on auth, try `assistant oauth ping google` (often refreshes an expired token); if that fails, load the `vellum-oauth-integrations` skill to reconnect.