Gmail
Judge each new email as it arrives, then archive, label, star or trash it, or save a draft reply.
Jev Events checks the inbox, asks Jev your questions about each new email, and runs what you pick on the answers. Choose below, and the code and the steps to run it fill in.
With your own Gmail, on your computer. The quickest way to try it.
import { monitor, recipes } from "jev-events";
import { google } from "@jev-events/google";
const inbox = monitor({
// Also judges the 5 latest emails, so there's something to see right away.
source: google.gmail.inbox({ backfill: 5 }),
questions: {
needsReply: recipes.email.needsReply,
},
// Native actions only log what they would do until you set dryRun: false.
dryRun: true,
})
.on("needsReply", { min: 0.8 }, google.gmail.label("Needs reply"));
await inbox.start();
console.log("Watching the inbox for new email. Stop with Ctrl-C.");
Run it
Make a folder for it, and install Jev Events and tsx, which runs TypeScript. Jev Events needs Node.js 22 or newer.
mkdir my-monitor && cd my-monitor npm init -y && npm pkg set type=module npm i jev-events @jev-events/google tsx
Sign in with your Google account. The first time, the command walks you through creating your own OAuth client in Google Cloud.
npx jev-events auth google
Put your TypeSafe API key in .env.
echo "TYPESAFE_API_KEY=<your key>" >> .env
Save the code as monitor.ts, and run it.
npx tsx --env-file=.env monitor.ts
Native actions log a [dry-run] would … line instead of running. When those look right, set dryRun: false.
Try it without code
To see what Jev says about your own mail before writing anything, install the integration, sign in once and watch. It prints an answer for the 5 latest emails, then for each new one:
npm i jev-events @jev-events/google
npx jev-events auth google
npx jev-events watch gmailBy default it asks whether each email needs a reply, whether it's urgent, and what kind of email it is. Ask your own with --ask "refund=Is this asking for a refund?". See the CLI.
The source
google.gmail.inbox() emits each new email in the inbox. It checks every 15 seconds; pass every: "1m" to monitor() to check less often.
| Option | Default | What it does |
|---|---|---|
backfill | 0 | Also emits this many of the latest emails on the first check |
label | "INBOX" | Watches another label instead, by its ID |
protect | Colleagues and people you've emailed | Who native actions never touch. false protects no one |
Actions
| Action | What it does |
|---|---|
google.gmail.archive() | Takes the email out of the inbox. It stays in All Mail. |
google.gmail.label(name) | Adds a label, creating it the first time. |
google.gmail.star() | Stars it. |
google.gmail.markRead() | Marks it as read. |
google.gmail.draftReply(text) | Saves a reply as a draft in the thread. It is never sent. |
google.gmail.trash() | Moves it to Trash, where Gmail keeps it for 30 days. |
text can be a string or a function of the event. Actions are dry-run until you pass dryRun: false to monitor().
Emails
Handlers receive a GmailItem:
| Field | Type | Notes |
|---|---|---|
subject | string | |
body | string | The new text, with quoted replies cut |
from, to, cc | { name?, address } | |
labels | string[] | Gmail label IDs, such as INBOX or IMPORTANT |
category | string | Gmail's inbox tab: personal, social, promotions, updates or forums |
mailingList | boolean | Sent through a list or bulk sender |
attachments | { filename, mimeType, size }[] | |
protectedBecause | string | Why native actions skip it, if they do |
Jev sees who wrote it, the subject and the new text, plus facts worked out from the account: whether it was sent to you directly, whether it's a reply, whether the sender is a colleague or someone you've emailed before, and its attachments.
Good to know
Protected by default. Native actions skip mail from people at your company and from people you've emailed before. Your company is your address's domain, and public ones such as gmail.com never count. Change it with protect: { addresses, except }, or turn it off with protect: false. A skipped action says why, for example colleague at acme.com.
Nothing is deleted or sent. The sign-in can't delete mail permanently. trash() is the only removal, and it can be undone for 30 days. draftReply() leaves a draft for a person to read and send.
One sign-in, Gmail and Calendar. google.app() asks for both. The builder asks only for what its monitor needs, such as google.app({ scopes: ["gmail"] }).
A server with one account of your own. google.fromEnv() builds the connection from GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and GOOGLE_REFRESH_TOKEN: await inbox.start({ connections: [google.fromEnv()] }).
Letting other people connect Gmail. While your consent screen is in Testing, only the test users you add can connect, and Google signs them out after 7 days. Before anyone else connects, Google has to verify your app. Gmail's scope is restricted, so it also needs a security assessment every year. Calendar only needs the verification.