scaffold the app: tauri crate, vite front end, icons, ci

This commit is contained in:
pj committed 2026-09-03 17:01:50 +05:30
commit 088ec9c6e4
267 files changed
+18617

No files matched your search

+749
View File
@@ -0,0 +1,749 @@
# Gmail API capability map
What the Gmail REST API (v1) and its neighbours can and cannot do for a native desktop
client (Tauri 2, Rust backend, React frontend) that wants HEY and Superhuman style
workflows on top of a user's Gmail account. Everything below was checked against the
live Google docs on 2026-09-03 unless marked "unverified" or "third-party".
One headline before the detail: the Gmail API usage limits changed on 1 May 2026. The
per-user rate limit is now 6,000 quota units per minute (100 per second, not the 250
per second most older write-ups quote), `messages.get` costs 20 units (not 5) and
`threads.get` costs 40 (not 10). Every quota figure in this document uses the new
table. Plan sync around it.
## 1. Auth and distribution
### Scopes
| Scope | Unlocks | Class |
|---|---|---|
| `https://mail.google.com/` | Everything, including permanent delete (`messages.delete`, `batchDelete`, `threads.delete`) and IMAP/SMTP via XOAUTH2. The only scope IMAP accepts. | Restricted |
| `https://www.googleapis.com/auth/gmail.modify` | Read, list, modify labels, trash/untrash, send, drafts, insert/import, history, watch. Not permanent delete. | Restricted |
| `https://www.googleapis.com/auth/gmail.readonly` | Read and list only, including history and watch. | Restricted |
| `https://www.googleapis.com/auth/gmail.compose` | Drafts create/update/send and `messages.send`. No reading. | Restricted |
| `https://www.googleapis.com/auth/gmail.send` | `messages.send` only. | Sensitive |
| `https://www.googleapis.com/auth/gmail.insert` | `messages.insert` and `messages.import`. | Restricted |
| `https://www.googleapis.com/auth/gmail.labels` | `labels.*` only. | Non-sensitive |
| `https://www.googleapis.com/auth/gmail.metadata` | List and read headers and labels, never bodies. `format=full` and `format=raw` are refused, and `messages.list` refuses the `q` parameter under this scope. | Restricted |
| `https://www.googleapis.com/auth/gmail.settings.basic` | Filters, vacation responder, IMAP/POP/language settings, update of the primary send-as (signature, display name). | Restricted |
| `https://www.googleapis.com/auth/gmail.settings.sharing` | Send-as alias create/delete/verify, forwarding addresses, auto-forwarding, delegates. Every one of these methods is documented as "only available to service account clients that have been delegated domain-wide authority", so a consumer desktop client cannot use them at all. | Restricted |
| `https://www.googleapis.com/auth/contacts.other.readonly` | People API `otherContacts.list` and `otherContacts.search` (the auto-collected "people you emailed" set that powers Gmail autocomplete). | Sensitive (third-party classification, unverified against Google's master list) |
| `https://www.googleapis.com/auth/contacts.readonly` | People API `people.connections.list`, `people.searchContacts`. | Sensitive (unverified, as above) |
| `https://www.googleapis.com/auth/calendar.events` | Calendar `events.list`, `events.patch`, `events.import` for RSVP. | Sensitive (unverified) |
| `https://www.googleapis.com/auth/pubsub` | Only needed if the client itself pulls from a Pub/Sub subscription. See section 3. | Cloud scope, not a Gmail scope |
Minimum set for the product as described: `gmail.modify` (which covers `gmail.labels`),
`gmail.settings.basic`, `contacts.other.readonly`, `contacts.readonly`, and
`calendar.events` if you do RSVP. Add `https://mail.google.com/` only if you use IMAP
or want permanent delete. Any Gmail scope beyond `gmail.send` and `gmail.labels` is
restricted, so there is no scope choice that avoids restricted-scope verification for a
real mail client.
### What verification means in practice
Publishing status and user caps, quoted from Google's "Manage App Audience" page:
- **Testing**: "up to 100 test users listed in the OAuth consent screen". Every test
user must be added by email address by the project owner. "Authorizations by a test
user will expire seven days from the time of consent", and that includes the refresh
token. The only exception is apps that request nothing beyond name, email and profile.
A mail client in Testing therefore forces a full re-login every 7 days. Do not ship
in Testing.
- **In production, unverified**: any Google account can be presented with the
"unverified app" interstitial and click through. The project gets "100 new users in
total" for its lifetime; the cap "cannot be reset". Refresh tokens do not have the
7-day expiry in this state.
- **In production, verified**: no cap, no interstitial (after brand verification the
app name and logo show on the consent screen).
Verification tiers and timelines:
- Brand verification (name and logo only): "typically takes 2-3 business days".
- Sensitive scope verification: "typically takes 3-5 business days". Requires a
domain verified in Search Console, a public homepage on that domain, a privacy
policy hosted on the same domain and linked from the consent screen, an unlisted
YouTube demo showing the consent flow with the client ID visible in the address bar
and each scope in use, and a written justification per scope.
- Restricted scope verification: all of the above plus Limited Use compliance, and the
process "can potentially take several weeks". Re-verification is annual.
- Security assessment (CASA, run by the App Defense Alliance): Google's wording is
"Every app that requests access to Google users' restricted data and has the ability
to access data from or through a third-party server must go through a security
assessment from Google-empanelled security assessors." Assessments are assigned an
assurance level (AL1 or AL2) and "All applications must be revalidated every year."
Third-party pricing for the common Tier 2 / lab-verified scan is roughly USD 540 to
1,800 per year (TAC Security via switchlabs.dev, 2025 to 2026); older figures of USD
15,000 to 75,000 (Nylas, 2021) refer to the original pentest regime and are stale.
The open question that decides Margin's cost: a desktop client whose only server is
Google, with all mail data on the user's disk, does not obviously "access data from or
through a third-party server". Google publishes no explicit "local-only apps are exempt"
clause, so treat this as unverified and ask the verification team in writing before
you build a push relay (a relay that sees only the user's email address and a
`historyId`, as Mimestream's does, may or may not count). Mimestream states it
"applied for and completed Google's Restricted scope verification process" and that
"Mimestream stores user email data locally on the user's device", but does not say
whether it was assessed.
Exemptions Google lists for verification itself: "personal use (fewer than 100
users)", apps in development/testing/staging, service-account-only apps, and internal
Workspace apps. None of these is a distribution strategy.
### Bring your own OAuth client
The pattern: each user creates their own Google Cloud project, enables the Gmail API,
configures an External consent screen, adds every scope, creates a "Desktop app" OAuth
client, and pastes the client ID and secret into Margin. Each user is then the
"developer" of a one-user app, which falls under the personal-use exemption and needs
no verification. It works, and several open-source tools ship this way.
It is not realistic for non-technical friends. It is fifteen to twenty clicks across a
console whose UI changes every few months, it needs the People API and Calendar API
enabled separately, the consent screen must either stay in Testing (7-day re-login)
or be pushed to production (scary interstitial, but stable tokens), and support
requests will all be "the Google page looks different". Offer it as an escape hatch
for the technical, not as the default.
### Installed-app OAuth flow
Use the loopback flow: OAuth client type "Desktop app", redirect to
`http://127.0.0.1:{port}` (or `http://[::1]:{port}`) served by a one-shot listener in the
Rust backend, PKCE mandatory (verifier 43 to 128 unreserved characters, `S256`
challenge). Google states "Custom URI schemes are no longer supported due to the risk
of app impersonation" and the out-of-band copy-paste flow is gone. The client secret
of a Desktop client is not a secret; Google's own doc says installed apps "cannot keep
secrets", so embedding it in the binary is expected.
Token facts to design around:
- "refresh tokens are always returned for installed applications".
- "limit of 100 refresh tokens per Google Account per OAuth 2.0 client ID". The oldest
is silently invalidated. Per-account, per-client, so a user on many machines is fine.
- A refresh token dies if unused for six months, if the user revokes it, or if "The
user changed passwords and the refresh token contains Gmail scopes". Password change
logs Margin out. Handle `invalid_grant` by prompting re-auth, not by crashing sync.
- Access tokens last about an hour. IMAP sessions authenticated with OAuth are "limited
to about the validity period of the access token used (usually 1 hour)".
Store refresh tokens in the OS keychain (macOS Keychain, Windows Credential Manager,
Secret Service on Linux), never in SQLite.
### What a small team shipping to friends should do
Create one Cloud project and one Desktop OAuth client. Push the consent screen to
production unverified straight away and eat the interstitial: it costs 100 lifetime
users, which is plenty for a friends release and avoids the 7-day token expiry that
Testing imposes. In parallel, buy a domain, publish a homepage and privacy policy on
it, record the demo video, and submit restricted scope verification with the explicit
statement that the app has no server and stores all Google user data on the user's
device; ask whether a security assessment is required. Budget for one anyway (order of
USD 1,000 to 2,000 per year at the cheap end). Do not request `https://mail.google.com/`
unless IMAP is in the plan; the narrower the scope list the easier the review.
Sources: https://developers.google.com/workspace/gmail/api/auth/scopes,
https://support.google.com/cloud/answer/15549945,
https://support.google.com/cloud/answer/7454865,
https://support.google.com/cloud/answer/13464323,
https://support.google.com/cloud/answer/13465431,
https://developers.google.com/identity/protocols/oauth2/production-readiness/restricted-scope-verification,
https://developers.google.com/identity/protocols/oauth2/production-readiness/sensitive-scope-verification,
https://developers.google.com/identity/protocols/oauth2/native-app,
https://developers.google.com/identity/protocols/oauth2 (refresh token expiration),
https://developers.google.com/terms/api-services-user-data-policy,
https://mimestream.com/trust/security-and-privacy,
https://www.switchlabs.dev/post/casa-tier-2-tier-3-security-review-providers-pricing-and-the-cheapest-option (third-party),
https://www.nylas.com/blog/google-oauth-app-verification/ (third-party, 2021).
## 2. Data model
Base URL is `https://gmail.googleapis.com/gmail/v1/users/{userId}/...`; use `me` for
`userId`. Uploads go to `https://gmail.googleapis.com/upload/gmail/v1/...`.
### Message
Fields: `id` ("The immutable ID of the message", a 16-hex-digit string), `threadId`,
`labelIds[]`, `snippet` ("A short part of the message text", HTML-escaped, roughly a
sentence), `historyId` ("The ID of the last history record that modified this
message"), `internalDate` ("The internal message creation timestamp (epoch ms)", the
time Gmail received or created it, not the `Date` header; for `insert`/`import` you
choose `internalDateSource=receivedTime|dateHeader`), `payload` (parsed MIME tree),
`sizeEstimate` (bytes, approximate), `raw` (base64url RFC 2822 bytes, only with
`format=raw`).
`format` on `messages.get` and `threads.get`:
| format | Returns | Notes |
|---|---|---|
| `minimal` | id, threadId, labelIds, snippet, historyId, internalDate, sizeEstimate | No headers. Same 20 units as `full`. |
| `metadata` | minimal plus `payload.headers` | Pass `metadataHeaders=From&metadataHeaders=Subject...` to restrict which headers come back. No body parts, no MIME tree. |
| `full` | Parsed MIME tree in `payload` | Body data is base64url in `payload.parts[].body.data`, or referenced by `attachmentId` when large. |
| `raw` | Whole RFC 2822 message base64url in `raw` | Best for archival and for feeding a real MIME parser (mail-parser or mailparse in Rust). |
`MessagePart`: `partId`, `mimeType`, `filename` (only present on attachments),
`headers[] {name, value}`, `body {attachmentId, size, data}`, `parts[]`. Body `data`
is base64url without padding issues in practice but always decode with a
URL-safe, padding-tolerant decoder. Large bodies and every real attachment arrive as
`attachmentId` and must be fetched separately with
`GET .../messages/{messageId}/attachments/{id}` (20 units, returns
`MessagePartBody {attachmentId, size, data}`). Inline images are ordinary parts whose
headers carry `Content-ID: <foo>` and `Content-Disposition: inline`; the HTML body
references them as `src="cid:foo"`, and the client maps cid to the part and serves the
bytes to the webview. Attachment IDs are widely reported to change between `get`
calls, so store the `partId`/`Content-ID` path, not the attachment ID (unverified in
the docs, consistently observed).
Sort and display by `internalDate`. The `Date` header is the sender's claim and can be
hours off or absent.
### Thread
`{id, snippet, historyId, messages[]}`. `threads.get` accepts the same `format` and
`metadataHeaders` and returns every message in the thread in one call for 40 units.
Threads cannot be created directly; Gmail assigns `threadId` on delivery, `send`,
`insert` or `import`. Threading rules are in section 4. Gmail's web UI splits a
conversation into a new thread with the same subject after 100 messages (third-party,
consistently reported, unverified in Google docs).
### Labels
System labels the guide lists, and whether `modify` may add or remove them:
| Label | Applicable via API |
|---|---|
| `INBOX` | Yes (remove to archive) |
| `SPAM` | Yes |
| `TRASH` | Yes (prefer `trash`/`untrash` methods) |
| `UNREAD` | Yes |
| `STARRED` | Yes |
| `IMPORTANT` | Yes |
| `SENT` | No |
| `DRAFT` | No |
| `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS` | Yes |
"The preceding list isn't exhaustive and other reserved label names exist" (`CHAT` is
one). Creating a user label with a reserved name returns "HTTP 400 - Invalid label
name". System label IDs equal their names; user label IDs look like `Label_42` and
are immutable, while names can be changed with `labels.patch`, so key everything on
ID and re-list labels when history shows anything unexpected.
User label fields: `name` (a `/` in the name nests the label in the Gmail UI, so
`Margin/Snoozed` renders under `Margin`; the parent label should exist first),
`messageListVisibility` (`show`, `hide`), `labelListVisibility` (`labelShow`,
`labelShowIfUnread`, `labelHide`), `color {textColor, backgroundColor}` restricted to
a fixed palette of about a hundred hex values listed in the Label resource docs (any
other value is rejected), `type` (`system`, `user`), and read-only counts
`messagesTotal`, `messagesUnread`, `threadsTotal`, `threadsUnread`. Hard limit:
"Maximum labels per mailbox: 10,000". Name length limit is not documented (the web
UI enforces 225 characters, unverified). Labels cannot be applied to drafts.
### Drafts
`{id, message}`. The draft `id` is stable across `drafts.update`; the inner
`message.id` changes on every update. `drafts.send` deletes the draft and returns a new
message with a new `id` and the `SENT` label. Draft messages only ever carry the
`DRAFT` label.
### History and historyId
`historyId` appears on every message, thread, the profile (`users.getProfile`, 1
unit, returns `emailAddress`, `messagesTotal`, `threadsTotal`, `historyId`), and every
`history.list` and `watch` response. Google: "History IDs increase chronologically but
are not contiguous with random gaps in between valid IDs." A history record is
`{id, messages[], messagesAdded[], messagesDeleted[], labelsAdded[], labelsRemoved[]}`
where the added/removed entries carry `{message: {id, threadId, labelIds?}, labelIds[]}`.
"We recommend using the specific change-type fields instead of" `messages[]`.
`messagesDeleted` means permanently deleted, not trashed; trash shows up as
`labelsAdded` with `TRASH`.
Stable IDs: message id, thread id, label id, draft id (until sent), historyId (as a
cursor). Not stable: the message id behind a draft, attachment ids (reported),
`snippet` text (can be regenerated), `resultSizeEstimate` (an estimate).
Sources: https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/Format,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages.attachments/get,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.threads,
https://developers.google.com/workspace/gmail/api/guides/labels,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.labels,
https://developers.google.com/workspace/gmail/api/guides/drafts,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.history/list,
https://support.cloudhq.net/how-does-gmail-decide-to-group-emails-into-conversations/ (third-party).
## 3. Sync strategy
### Quota, the numbers that matter
"As of May 1, 2026, the usage limits for this API were updated."
| Limit | Value |
|---|---|
| Per minute per user per project | 6,000 quota units |
| Per minute per project | 1,200,000 quota units |
| Per day per project (billing threshold) | 80,000,000 quota units |
| Batch | 100 calls max per batch, "larger than 50 requests is not recommended", counted as n requests |
| Pricing | "All standard use of the Gmail API is available at no additional cost. Exceeding the quota request limits is planned to incur charges to your Google Cloud billing account later in 2026." |
Per-method costs (units):
| Method | Units | Method | Units |
|---|---|---|---|
| `messages.send`, `drafts.send`, `watch` | 100 | `messages.get`, `drafts.get`, `messages.attachments.get`, `messages.trash` | 20 |
| `threads.get` | 40 | `threads.trash`, `threads.delete` | 20 |
| `messages.batchModify`, `messages.batchDelete`, `stop` | 50 | `messages.insert`, `messages.import` | 25 |
| `drafts.update` | 15 | `messages.delete`, `drafts.create`, `drafts.delete`, `threads.list`, `threads.modify` | 10 |
| `messages.list`, `drafts.list`, `messages.modify`, `messages.untrash`, `labels.create/delete/update`, `settings.filters.create/delete`, `settings.updateVacation` | 5 | `history.list` | 2 |
| `getProfile`, `labels.get`, `labels.list`, `settings.filters.get/list`, `settings.getVacation`, `settings.getAutoForwarding` | 1 | `settings.sendAs.create/update/verify`, `forwardingAddresses.create`, `delegates.create`, `updatePop` | 100 |
Third-party pages dated 2026 still quote `messages.get` at 5 and `threads.get` at 10;
Google's page as read on 2026-09-03 says 20 and 40. There is also an undocumented
concurrency ceiling per mailbox (Unipile reports "50 concurrent in-flight requests
per mailbox"; Google's error page only says 429 can be "triggered by daily per-user
limits, bandwidth limits, or concurrent request limits" and "Per-user limits cannot
be increased").
What 6,000 units per minute buys: 300 `messages.get` per minute, or 150 `threads.get`,
or 60 sends. A mailbox with 20,000 messages costs 400,000 units to fetch metadata for
every message, which is 67 minutes at the ceiling; 100,000 messages is 5.6 hours.
Idle polling of `history.list` every 15 seconds is 8 units per minute, so polling is
free; hydration is what costs.
### Initial full sync
1. `getProfile` and record `historyId` before you list anything, so the first partial
sync covers everything that changed during the crawl.
2. `messages.list?maxResults=500&includeSpamTrash=true` (5 units per page, ids and
threadIds only, newest first) and page with `pageToken` until exhausted. 20,000
messages is 40 pages, 200 units, a few seconds. Store ids, threadIds and a
"needs hydration" flag.
3. Hydrate newest first in batches of 50 `messages.get?format=metadata` with
`metadataHeaders` limited to what the UI and the classifier need: `From`, `To`,
`Cc`, `Bcc`, `Reply-To`, `Subject`, `Date`, `Message-ID`, `In-Reply-To`,
`References`, `List-Id`, `List-Unsubscribe`, `List-Unsubscribe-Post`, `Precedence`,
`Authentication-Results`, `Content-Type`. Throttle to roughly 280 gets per
minute, retry 429 and 403 `userRateLimitExceeded` with truncated exponential
backoff starting at 1 second, and treat 404 as "deleted since listing".
4. For threads with three or more messages, `threads.get?format=metadata` (40 units)
is cheaper than per-message gets. Group step 2's ids by threadId and route.
5. Bodies: fetch `format=full` (or `raw`) lazily on open and prefetch the last N days
in the background. Cache decoded HTML and text in SQLite with an LRU cap.
6. Labels: `labels.list` (1 unit) once, then again whenever history mentions a label
id you do not know.
Order of hydration is a product decision: newest 30 days first, then the rest, and the
UI must be usable while step 3 is running. Zero (Mail-0) reports initial sync "can take
hours for large inboxes (10,000+ emails)"; with the new unit costs that is the norm,
not the exception.
### Partial sync
`history.list?startHistoryId=X&maxResults=500` (2 units per page), optionally
`historyTypes=messageAdded|messageDeleted|labelAdded|labelRemoved` and `labelId=`.
Apply pages in order; the records are chronological by `id`. For each `messagesAdded`
run `messages.get` (the record includes `labelIds`, but not headers); for
`labelsAdded`/`labelsRemoved` update the local label set; for `messagesDeleted`
delete locally. A message can appear in `messagesAdded` and `messagesDeleted` in the
same window, and `messages.get` can 404 for a record you have not processed yet;
both are normal, not errors. When the final page has no `nextPageToken`, store the
response `historyId` as the new cursor. Never combine `q` with history; history is
mailbox-wide and has no query parameter, so every screening decision is made locally
on the record's `labelIds` and the fetched headers.
Google on validity: "A historyId is typically valid for at least a week, but in some
rare circumstances may be valid for only a few hours. If you receive an HTTP 404 error
response, your application should perform a full sync." The cheap recovery is: re-run
step 2 of the full sync (ids only, 5 units per 500), diff against the local set to
find adds and deletes, hydrate the adds, and refresh `labelIds` for the most recent N
days with `format=minimal` (still 20 units each; there is no cheaper label-only read
except `threads.list`, which returns only ids). Accept that label state for old mail
may be stale until the user opens it.
### Polling cadence
Poll `history.list` every 10 to 15 seconds while the window is focused, 60 seconds in
the background, and immediately after any local write. Cost at 15 seconds is 5,760
units per user per day; 1,000 users idle-polling this way is 5.8 million units per
day against the 80 million project threshold. The budget goes to hydration and full
syncs, not polling.
### Push via Pub/Sub, honestly
`users.watch` (100 units) with `topicName=projects/{project}/topics/{topic}` where the
project "must exactly match your Google developer project id (the one executing this
watch request)". You grant `roles/pubsub.publisher` on the topic to
`[email protected]`. Watches expire after 7 days and Google
says "We recommend calling watch once per day". The notification is only
`{"emailAddress": "...", "historyId": "..."}` and the rate is capped at "one event per
second" per user. On receipt you run the partial sync above.
Delivery needs a Pub/Sub subscription. A push subscription posts to an HTTPS endpoint,
which means a server. A pull subscription is possible from a desktop app in theory:
`projects.subscriptions.pull` needs the `pubsub.subscriptions.consume` permission
(`roles/pubsub.subscriber`) on the subscription and a token with
`https://www.googleapis.com/auth/pubsub` or `cloud-platform`. The problem is the
principal. The desktop app authenticates as the end user's Google account, which has
no IAM on your project. Your options are to grant every user individually (a server
or manual step), grant `allAuthenticatedUsers` (any Google account on earth could pull
and ack everyone's notifications from the shared subscription), or ship a service
account key in the binary (extractable, and Google will reject it in review). None is
acceptable. Conclusion: with no server there is no push. If you want push later, the
minimum is a small relay that owns the subscription and forwards `{emailAddress,
historyId}` to connected clients; Mimestream runs exactly that (`push.mimestream.com`)
and stores only "Email address, APNs device token, Device identifier". That relay may
also drag you into the security assessment. Ship with polling.
### IMAP as a complement
Gmail IMAP (`imap.gmail.com:993`, SMTP `smtp.gmail.com:465` or `587`) authenticates with
SASL XOAUTH2 (`base64("user=" user "^Aauth=Bearer " token "^A^A")`) using the
`https://mail.google.com/` scope, which is restricted like the rest. Capabilities
include `IDLE`, `CONDSTORE`, `MOVE`, `UIDPLUS`, `X-GM-EXT-1` and `APPENDLIMIT=35651584`;
there is no `QRESYNC`. The Gmail extensions expose `X-GM-MSGID` (64-bit message id),
`X-GM-THRID` (thread id), `X-GM-LABELS` (fetch, store and search labels) and
`X-GM-RAW` (full Gmail search syntax over IMAP). The REST `id` is the lowercase hex of
`X-GM-MSGID` and `threadId` the hex of `X-GM-THRID` (widely relied on, verify in a
spike before depending on it).
What IMAP does that REST cannot: bulk header fetch with no unit quota (one `UID FETCH
1:* (ENVELOPE BODYSTRUCTURE X-GM-LABELS X-GM-THRID X-GM-MSGID)` over `[Gmail]/All Mail`
returns tens of thousands of messages in minutes, limited only by the 2,500 MB per day
IMAP download bandwidth), and `IDLE` for near-instant new-mail notification without a
server (one connection per watched folder, sessions "limited to about 24 hours" and
OAuth sessions to the token lifetime, so reconnect hourly). What REST does that IMAP
cannot: `history.list` (IMAP has no mailbox-wide change log; `CONDSTORE` gives per-folder
`MODSEQ` only), filters, settings, send-as, vacation, drafts with stable ids, proper
`SENT` threading via `threadId`, snippets, `sizeEstimate`, category labels as first-class
ids, and batch HTTP. IMAP exceeding bandwidth suspends the account for "1 hour, but can
last up to 24 hours".
Reasonable hybrid: REST for everything the user touches and for the change log; IMAP
only as an optional accelerator for the initial backfill and for `IDLE` as a wake-up
signal that triggers `history.list`. That costs you the `https://mail.google.com/`
scope and a second protocol stack, so do it only if the initial sync time measured
with REST alone is unacceptable.
### Local mirror
Mimestream keeps everything in a Core Data SQLite store on the Mac and syncs "directly
with Google APIs, not through an intermediary service". Do the same: tables for
accounts (email, historyId cursor, profile), labels (id, name, type, visibility,
colour), messages (id, threadId, internalDate, sizeEstimate, snippet, labelIds as a
JSON array or a join table, parsed headers as columns, hydration state), threads
(id, latest internalDate, participant summary, derived flags), bodies (message id,
html, text, fetched_at), attachments (message id, partId, filename, mimeType, size,
content-id, cached path), an outbox, and an FTS5 table over subject, participants and
body text. Everything in section 5 marked "client-side" also lives here.
Sources: https://developers.google.com/workspace/gmail/api/reference/quota,
https://developers.google.com/workspace/gmail/api/guides/sync,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.history/list,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/list,
https://developers.google.com/workspace/gmail/api/guides/batch,
https://developers.google.com/workspace/gmail/api/guides/handle-errors,
https://developers.google.com/workspace/gmail/api/guides/push,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users/watch,
https://docs.cloud.google.com/pubsub/docs/access-control,
https://docs.cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions/pull,
https://mimestream.com/trust/private-push,
https://developers.google.com/workspace/gmail/imap/imap-extensions,
https://developers.google.com/workspace/gmail/imap/xoauth2-protocol,
https://developers.google.com/workspace/gmail/imap/imap-smtp,
https://knowledge.workspace.google.com/admin/gmail/gmail-bandwidth-limits,
https://gist.github.com/emersion/2c769bc1ed60a7b7945910d35b606801 (third-party capability dump),
https://www.unipile.com/gmail-api-limits/ (third-party).
## 4. Writes
### Labels and state
- `messages.modify` (5 units): `{addLabelIds[], removeLabelIds[]}`.
- `messages.batchModify` (50 units): same body plus `ids[]`, "There is a limit of 1000
ids per request", empty response, no per-id error report.
- `threads.modify` (10 units): applies to every message in the thread. Note that
"Messages in a thread might have labels that other messages in the same thread
don't have", so thread-level views must aggregate.
- Read/unread: remove/add `UNREAD`. Star: `STARRED`. Importance: `IMPORTANT`. Archive:
remove `INBOX`. Spam: add `SPAM` (removes from inbox in Gmail's view). Category
move: remove one `CATEGORY_*`, add another.
- Trash: `messages.trash` / `threads.trash` (20) and `untrash` (5). Gmail purges trash
after 30 days. Permanent delete: `messages.delete` (10), `batchDelete` (50),
`threads.delete` (20); these need `https://mail.google.com/` (long-standing,
unverified in this pass).
- Mute: there is no mute label or method. `q=is:muted` does find muted threads, so
the state is readable but not writable. Emulate with a `Margin/Muted` label and a
client rule that strips `INBOX` from new messages in muted threads as they arrive in
history. While Margin is not running, Gmail web will show them in the inbox.
### Sending
`messages.send` (100 units): body `{raw, threadId?}` where `raw` is the complete RFC
2822 message base64url-encoded. Simple JSON body is fine for small messages; for
anything with attachments use the upload URI with `uploadType=multipart` or
`resumable`. Discovery document `maxSize` for send is 36,700,160 bytes (35 MB) for the
whole encoded message; the Gmail web UI limit for attachments is 25 MB (unverified).
`insert` and `import` accept 157,286,400 bytes (150 MB). Consumer accounts are capped
at 500 messages per day and Workspace at 2,000 (Google's help page confirms the 500;
the 2,000 is third-party), counted across web, IMAP and API.
Threading rules for a reply, all three required: the `threadId` in the request, and
"The References and In-Reply-To headers must be set in compliance with the RFC 2822
standard", and "The Subject headers must match" (Gmail tolerates `Re:`, `Fwd:`, `R:`
and similar prefixes). Since the March 2019 change Gmail also requires that "an
incoming message's Reference header, if present, must reference IDs of previous
messages"; same subject and participants alone no longer thread. Practical recipe:
`In-Reply-To: <parent Message-ID>`, `References: <parent References> <parent
Message-ID>`, subject `Re: <original>`, `threadId` of the parent. Changing the subject
starts a new thread on the recipient's side.
Other send facts: the `From` header must be the account's primary address or a
verified send-as alias, otherwise Gmail rewrites it to the primary (documented
behaviour of aliases, rewriting itself reported by developers, unverified in the
reference). `labelIds` in the send body should be assumed ignored; apply labels with
`messages.modify` on the returned id (unverified). Gmail generates a `Message-ID` if
you omit one; supplying your own lets you correlate the outbox with the sent copy.
The response contains the new `id`, `threadId` and `labelIds` (`SENT`). There is no
scheduled send and no undo send in the API; Gmail web's Undo Send is a client-side
hold of 5, 10, 20 or 30 seconds before the message leaves, which is exactly what
Margin should implement.
### Drafts
`drafts.create` (10), `drafts.update` (15), `drafts.get` (20), `drafts.list` (5),
`drafts.delete` (10), `drafts.send` (100, "You can send as-is or provide updates by
including a new MIME message"). Put `threadId` inside `message` for reply drafts.
Drafts created via the API appear in Gmail web's Drafts folder, which is the only
roaming compose state you get.
### Settings
- `settings.sendAs.list/get`: enumerate the primary address and aliases with
`displayName`, `replyToAddress`, `signature` (HTML, "added to new emails only",
Gmail "will sanitize the HTML before saving it"), `isPrimary`, `isDefault`,
`treatAsAlias`, `verificationStatus` (`accepted`, `pending`). `sendAs.update/patch`
on the primary address works with `gmail.settings.basic`; "Addresses other than the
primary address for the account can only be updated by service account clients that
have been delegated domain-wide authority", and `create`, `delete`, `verify` are
service-account-only too. So: read aliases, send from them, edit the primary
signature; nothing else.
- `settings.getVacation/updateVacation` (`gmail.settings.basic`):
`{enableAutoReply, responseSubject, responseBodyPlainText, responseBodyHtml,
restrictToContacts, restrictToDomain, startTime, endTime}`.
- Filters (`gmail.settings.basic`): `settings.filters.create/get/list/delete`, no
update ("filters must be deleted and recreated"), "you can only create a maximum of
1,000 filters". Criteria: `from`, `to` (matches the local part, case-insensitive),
`subject`, `query` (Gmail search syntax), `negatedQuery`, `hasAttachment`,
`excludeChats`, `size` with `sizeComparison` (`smaller`, `larger`). Actions:
`addLabelIds[]`, `removeLabelIds[]`, `forward` (needs a verified forwarding address,
which you cannot create). The guide's examples use `INBOX` and `UNREAD` in
`removeLabelIds` (skip inbox, mark read), `TRASH`, `STARRED`, `IMPORTANT` in
`addLabelIds`, and one user label per filter; the exact accepted set is not spelled
out. "Filters only apply to specific messages and not the entire email thread", and
they act on mail as it arrives; the web UI's "also apply filter to matching
conversations" has no API equivalent, so backfilling is your job with `batchModify`.
- Forwarding addresses, auto-forwarding, delegates, POP/IMAP toggles: all
service-account-only or admin territory. Delegates are Workspace-only ("up to 25
delegates and up to 10 delegators").
- `messages.import` (25 units, 150 MB) runs "standard email delivery scanning and
classification similar to receiving via SMTP" including `processForCalendar`;
`messages.insert` just stores. Neither sends. Useful for migration, not for the
features here.
Sources: https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/batchModify,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/send,
https://gmail.googleapis.com/$discovery/rest?version=v1 (maxSize values),
https://developers.google.com/workspace/gmail/api/guides/sending,
https://developers.google.com/workspace/gmail/api/guides/threads,
https://workspaceupdates.googleblog.com/2019/03/threading-changes-in-gmail-conversation-view.html,
https://developers.google.com/workspace/gmail/api/guides/drafts,
https://developers.google.com/workspace/gmail/api/guides/alias_and_signature_settings,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.sendAs/create,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.sendAs/update,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings/updateVacation,
https://developers.google.com/workspace/gmail/api/guides/filter_settings,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.filters,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.filters/create,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.forwardingAddresses/create,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings/updateAutoForwarding,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.delegates/create,
https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/import,
https://support.google.com/mail/answer/22839, https://support.google.com/mail/answer/2819488.
## 5. Feature feasibility matrix
"Native" means the Gmail API (or the named Google API) does it as a first-class
operation. "Emulate" means Margin builds it from labels, filters, headers and local
state. Anything emulated with local state does not roam to another machine or to
Gmail web unless the label mirror carries it.
| Feature | Native in Gmail API | Emulate client-side (how) | Not possible |
|---|---|---|---|
| Screener (allow-list senders, hide unknown senders from inbox) | No. Filters cannot express "sender not in a list" beyond a single `negatedQuery` capped by query length. | On every `messagesAdded` with `INBOX`, look the sender up in a local allow-list (contacts, prior correspondents, user decisions). Unknown: `modify` remove `INBOX`, add `Margin/Screened out`. Screen-in decision re-adds `INBOX` for the thread and records the sender. Only runs while Margin is running; Gmail web shows unscreened mail in Inbox in between. | Server-side enforcement without a server. |
| Split inbox / Feed / Paper trail | Partial. `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_SOCIAL`, `CATEGORY_FORUMS`, `CATEGORY_PERSONAL` are applied by Gmail's classifier regardless of whether tabs are on (third-party), and are readable and settable. | Per-sender routing stored locally and mirrored as `Margin/Feed`, `Margin/Paper trail` labels via `modify`; optionally a Gmail filter per stable sender (`from:` add label, remove `INBOX`) so Gmail web matches and mail arriving while Margin is closed is routed. Seed the initial bucket from `CATEGORY_*` and `List-Id`/`Precedence: bulk` headers; let the user override per sender. | |
| Reply later pile | No. | Label `Margin/Reply later` plus local ordering and optional local reminders. Keep or drop `INBOX` as a product choice. | |
| Set aside pile | No. | Label `Margin/Set aside`, remove `INBOX`. | |
| Snooze | No. Gmail's own snooze is invisible except through `q=in:snoozed` and the eventual `INBOX` label add/remove in history; no snooze date is exposed (issue 287304309 is open). | Remove `INBOX`, add `Margin/Snoozed`, store wake time in SQLite; a scheduler re-adds `INBOX` (and `UNREAD` if wanted). Fires late if Margin was closed. Do not touch messages the user snoozed in Gmail web; Mimestream documents a Gmail server bug where archiving previously-snoozed mail via the API leaves it showing in Inbox. | Cross-device wake without your own sync. |
| Send later | No. Gmail web's Schedule send is not in the API. | Local outbox row with `send_at`; scheduler calls `messages.send` when due. Store the compose as a Gmail draft too so it survives a reinstall. App must be running at send time. | Sending while the app is closed. |
| Undo send | No. | Hold the message in the outbox for N seconds (Gmail web offers 5, 10, 20, 30) before calling `messages.send`. After send it is gone. | Recall after send. |
| Read receipts / open tracking | No. Gmail's read receipts are Workspace-only and admin-enabled (`Disposition-Notification-To`); consumer accounts cannot request or return them. | Adding `Disposition-Notification-To` to `raw` costs nothing but only some recipients' clients honour it (unverified). Pixel tracking needs a server you run and is the same practice Margin blocks on receipt; recommend not building it. | Open tracking without a server. |
| Spy pixel blocking | No. | Load bodies in the webview with remote content blocked by default, rewrite `<img src=http...>` to placeholders, allow per-sender or per-message, strip 1x1 and known tracker hosts, never send `Referer`. Optional proxy through your own server later. | |
| Unsubscribe | No (Gmail web's button is UI only). | Fetch `List-Unsubscribe` and `List-Unsubscribe-Post` via `metadataHeaders`. If `List-Unsubscribe-Post: List-Unsubscribe=One-Click` is present, POST to the HTTPS URI with body `List-Unsubscribe=One-Click` (RFC 8058, no GET, no confirmation page). Else `mailto:` via `messages.send`, else open the HTTPS link. Pair with a `from:` filter to `TRASH` or `Margin/Screened out` for stragglers. | |
| Block sender | Yes via filters: `criteria.from`, `action.addLabelIds=[TRASH]` (or `removeLabelIds=[INBOX]` plus a label). | Backfill existing mail with `messages.list?q=from:x` and `batchModify`. | |
| Snippets / templates | No. | Local SQLite; insert into compose. | |
| Sticky notes on threads | No. | Local SQLite keyed by threadId. Hidden drafts in the thread show up in Gmail web and labels cannot carry text, so there is no sane Gmail-side store. | Roaming without your own sync. |
| Rename thread subject | No; message headers are immutable. | Local display-name override keyed by threadId; replies still carry the real subject so threading holds. | Changing what Gmail web shows. |
| Merge threads | No; `threadId` is assigned by Gmail. | Local mapping of several threadIds to one virtual thread; writes fan out to every underlying thread. | Merging in Gmail itself. |
| Contacts autocomplete | Yes via People API: `otherContacts.list` (`contacts.other.readonly`, `readMask=names,emailAddresses`, pageSize up to 1000, sync tokens expire after 7 days), `people.connections.list` (`contacts.readonly`, up to 1000), `otherContacts.search` and `people.searchContacts` (max 30 results, send an empty-query warm-up first). | Also index From/To/Cc from synced headers locally; it is the best signal and needs no extra scope. | |
| Attachments browsing | Partial: `q=has:attachment` or `filename:pdf` returns ids. | Index `payload.parts[].filename/mimeType/size` from `format=full` fetches into SQLite; fetch bytes on demand with `attachments.get`. | |
| Calendar invite RSVP | Not in Gmail API. Calendar API: find the event with `events.list?iCalUID=<UID from the text/calendar part>` on `primary` (Gmail auto-adds invites depending on the user's "Add invitations to my calendar" setting, "From everyone" or "Only if the sender is known"), then `events.patch` with `attendees[self].responseStatus` in `accepted`, `tentative`, `declined` and `sendUpdates=all`; if absent, `events.import` with `iCalUID`, `start`, `end`. Needs `calendar.events`. | Parse the `text/calendar` part (`METHOD:REQUEST`) yourself and render the card. A standards-only fallback is to email a `METHOD:REPLY` iMIP message to the organiser via `messages.send`, which needs no Calendar scope but does not update the user's own calendar. | |
| Shared / team features | Out of scope. Delegation exists but is Workspace-only and service-account-only. | | |
| Multiple accounts | Yes: one OAuth grant per account with the same client ID; separate refresh tokens and sync cursors. | | |
| Unified inbox | No. | Merge locally across account databases; every write routes to the owning account. | |
| Search | Yes: `messages.list?q=` and `threads.list?q=` (5 or 10 units per page, full Gmail operator set: `from:`, `to:`, `subject:`, `label:`, `category:`, `has:attachment`, `filename:`, `list:`, `newer_than:`, `larger:`, `rfc822msgid:`, `in:snoozed`, `is:muted`, `deliveredto:`, `AROUND`, `OR`, `-`). Returns ids only; hydrate from the mirror. Not available under `gmail.metadata`. Cannot be combined with `history.list`. | SQLite FTS5 over synced subject, participants and cached bodies for instant results; fall back to server `q` for anything not yet hydrated and for operators you have not implemented. Server search is a network round trip and returns ids in relevance-ish order, so results feel slower than local. | |
| Keyboard triage | No. | Client. Batch consecutive label changes into `batchModify` calls. | |
| Push notifications | Yes with `users.watch` plus Pub/Sub, but delivery requires a server or an unacceptable IAM grant (section 3). | Poll `history.list`; optionally IMAP `IDLE` on `INBOX` as a wake-up. | Push with no server. |
| Offline compose and queue | No. | Local outbox; `drafts.create` when online so the draft roams; `messages.send` on reconnect with backoff. | |
| Categories tabs | Yes: read and set `CATEGORY_*`. Accuracy is good for big senders and mediocre for small newsletters; Promotions is a fair Feed seed, Updates is a fair Paper trail seed (receipts, notifications), Forums and Social less useful. Gmail may reclassify a sender after the user moves a few messages, which you observe as label churn in history. | Treat as a prior; sender-level rules win. | |
| Importance markers | Yes: `IMPORTANT` label readable and settable; filters can `removeLabelIds=[IMPORTANT]`. | | |
| Muted threads | Readable via `q=is:muted`, not settable. | `Margin/Muted` label plus client rule (section 4). | Muting that Gmail itself enforces. |
| Labels and folders, archive, star, read state, trash, spam | Yes. | | |
| Signature and vacation responder | Yes for the primary address (`sendAs.patch` signature, `updateVacation`). | | Creating or verifying send-as aliases (service-account-only). |
| Permanent delete | Yes, but only with `https://mail.google.com/`. | Trash instead. | |
Sources: https://developers.google.com/workspace/gmail/api/guides/labels,
https://developers.google.com/workspace/gmail/api/guides/filter_settings,
https://issuetracker.google.com/issues/287304309 (login required),
https://mimestream.com/help/common-issues/archiving-snoozed-messages,
https://support.google.com/mail/answer/9413651,
https://www.rfc-editor.org/rfc/rfc8058,
https://developers.google.com/people/api/rest/v1/otherContacts/list,
https://developers.google.com/people/api/rest/v1/otherContacts/search,
https://developers.google.com/people/api/rest/v1/people.connections/list,
https://developers.google.com/people/api/rest/v1/people/searchContacts,
https://developers.google.com/workspace/calendar/api/v3/reference/events,
https://developers.google.com/workspace/calendar/api/v3/reference/events/list,
https://developers.google.com/workspace/calendar/api/v3/reference/events/import,
https://support.google.com/calendar/answer/13159188,
https://support.google.com/mail/answer/7190 (search operators),
https://support.google.com/mail/thread/231661001 (categories applied with tabs off, community thread).
## 6. Gotchas and war stories
- Quota changed on 1 May 2026. Any library, blog post or mental model built on 250
units per second per user and 5-unit `messages.get` is off by a factor of 4 to 10.
Budget hydration at 300 messages per minute per user and expect billing for overage
"later in 2026".
- Full sync is now the slow part. A 100,000 message mailbox is roughly 5 to 6 hours of
metadata hydration at the ceiling. Hydrate newest first, show partial state, and
consider IMAP for the backfill if that is unacceptable.
- `history.list` 404 is routine. A user who does not open Margin for a week or two
will hit it; treat the ids-only re-list as a normal code path, not a disaster.
- History can reference messages that no longer exist. `messages.get` 404 on a
`messagesAdded` id means it was deleted in the same window. Log and move on.
- Label IDs are not names. `Label_123` is the key; names change; system labels are
their own IDs. Re-list labels when history contains an unknown id. Do not create a
label whose name collides with a reserved name (400).
- `labelIds` on `messages.send` should be assumed ignored; `modify` afterwards.
- `raw` is base64url, not standard base64. `+`/`/` versus `-`/`_` mistakes produce
corrupt MIME that Gmail rejects with a 400 that says nothing useful.
- 35 MB (36,700,160 bytes) is the hard `send` ceiling for the whole encoded message,
which after base64 expansion is about 25 MB of attachments. Fail early in the
composer, not at send.
- Messages are immutable. There is no way to change headers, subject, or body of a
stored message. Every "edit" feature in section 5 is a local overlay.
- `messages.list` pages at most 500 and returns only ids; `resultSizeEstimate` is an
estimate and can be wildly wrong. `threads.list` likewise.
- Batch: 100 max, 50 recommended, each call metered separately, and 429 inside a batch
comes back per part, so parse every part's status. The batch path from the discovery
document is `batch/gmail/v1` on `www.googleapis.com`.
- 429 and 403 `userRateLimitExceeded` / `rateLimitExceeded` both want truncated
exponential backoff (`min((2^n) + random_ms, 32 to 64 s)`) and "Start retry periods
at least one second after the error". Per-user limits "cannot be increased for any
reason". Watch the undocumented concurrency ceiling: keep in-flight requests per
mailbox low (single digits) and the batch size at 25 to 50.
- Server search is slow-ish and eventually consistent: a message you just labelled may
not match `q=label:x` for a few seconds. Local FTS is the fast path.
- Header decoding: RFC 2047 encoded-words (`=?UTF-8?B?...?=`) in From, Subject and
filenames, RFC 2231 parameter continuations in `Content-Disposition`, and
charset-labelled bodies (`iso-8859-1`, `windows-1252`, `gb2312`) all show up daily.
Use a real MIME parser on `raw` rather than trusting `payload` for anything beyond
headers.
- HTML mail in a webview: sanitise (strip scripts, forms, `<meta http-equiv>`,
external CSS, `javascript:` URLs), isolate in an iframe or Tauri child webview with
CSP, block remote images by default, rewrite `cid:` to local resources, and expect
broken layouts with dark mode. Practical dark-mode approach is per-message opt-in
inversion or a light-background iframe; automatic colour inversion breaks a lot of
newsletters.
- Newsletter and bulk detection: `List-Id` (RFC 2919), `List-Unsubscribe`,
`Precedence: bulk|list`, `Auto-Submitted`, `X-Mailer` and a `CATEGORY_PROMOTIONS`
or `CATEGORY_UPDATES` label together give a reliable "not a human" signal for the
screener's default bucket.
- Authentication results are only in raw headers. Fetch `Authentication-Results` via
`metadataHeaders` and parse `spf=`, `dkim=`, `dmarc=` from the `mx.google.com` stanza.
There is no API field for spam score or phishing verdict; `SPAM` label is all you get.
- Gmail's classifier and your screener will disagree. A first-time human sender in
Promotions is common (Gmail keys on content, you key on sender). Make the screener
decision sender-based and use categories only to pick the default bucket.
- Threading edge cases: recipients and senders can see different threads for the same
messages; a changed subject starts a new thread; Gmail web splits at 100 messages;
mailing-list software that rewrites `Message-ID` or strips `References` produces
orphan threads; a reply sent without `threadId` lands in a new thread even with
correct headers.
- Snoozed-in-Gmail messages that Margin archives can reappear in Gmail web's Inbox
(server bug, Mimestream help page). Leave Gmail-snoozed mail alone.
- Password change revokes every refresh token with Gmail scopes. Expect
`invalid_grant` and re-prompt.
- Testing-mode tokens die after 7 days. If someone reports "it logs me out weekly", the
consent screen is still in Testing.
- `resultSizeEstimate`, `messagesTotal` and label counts are estimates and lag. Never
drive UI badges from them; count locally.
- People API sync tokens expire after 7 days and "The first page of a full sync request
has an additional quota" that returns 429 if hammered; do one full contacts sync per
install, then incremental.
- Sending limits are per account across all clients: 500 per day consumer. A bulk
"unsubscribe from 200 lists via mailto" action can burn a meaningful chunk of it.
Sources: https://developers.google.com/workspace/gmail/api/guides/handle-errors,
https://developers.google.com/workspace/gmail/api/reference/quota,
https://developers.google.com/workspace/gmail/api/guides/sync,
https://developers.google.com/workspace/gmail/api/guides/batch,
https://developers.google.com/workspace/gmail/api/guides/threads,
https://mimestream.com/help/common-issues/archiving-snoozed-messages,
https://developers.google.com/identity/protocols/oauth2,
https://developers.google.com/people/api/rest/v1/otherContacts/list.
## 7. Recommendation
REST is the only transport for v1. IMAP comes later, if at all, for two narrow jobs:
bulk backfill when measured REST sync time is unacceptable, and `IDLE` as a wake-up
signal. `history.list` stays the single change log; IMAP has no equivalent.
Request `gmail.modify`, `gmail.settings.basic`, `contacts.other.readonly`,
`contacts.readonly` and `calendar.events`; no `https://mail.google.com/` until IMAP is
real. Push the consent screen to production unverified for the friends release (100
lifetime users, stable tokens) and file restricted-scope verification now, stating
there is no server and asking in writing whether the assessment applies.
Everything lives in SQLite on the device: message and thread mirror, bodies with an
LRU, FTS5 index, per-account history cursor, and an outbox with `send_at` and
`hold_until` (send later and undo send are one mechanism). Poll `history.list` every
10 to 15 seconds in the foreground. No server, no push; say so in the product.
Mirror every pile as a real label under one namespace so Gmail web stays coherent:
`Margin/Screened out`, `Margin/Feed`, `Margin/Paper trail`, `Margin/Reply later`,
`Margin/Set aside`, `Margin/Snoozed`, `Margin/Muted`. All but Reply later also remove
`INBOX`, so Gmail web's inbox matches Margin's and the rest is findable by label. For
senders the user has explicitly routed, also create a `from:` filter so routing works
while Margin is closed; stay well under the 1,000 filter cap.
What cannot live in Gmail, and so does not roam to another device or to Gmail web
until Margin ships its own sync: sticky notes, thread renames and merges, snooze wake
times, scheduled send times, the allow-list and per-sender rules (labels show the
outcome, not the rule), snippets. A second machine sees the labels, not the rules,
and a snooze set on the laptop will not wake the desktop. Call this "single device of
record" in the onboarding copy rather than discovering it in support.
Skip open tracking: it needs a server, contradicts the pixel blocking, and invites
scrutiny during review. Design against these numbers: 6,000 units per minute per
user, 20 units per metadata fetch, 100 per send, 35 MB per message, 500 sends per
day, 1,000 filters, 10,000 labels, 500 ids per page, 1,000 ids per `batchModify`, 100
calls per batch, history validity that can drop to hours, and no API for snooze,
schedule send, undo send, mute, rename, notes, or push without a server.
+157
View File
@@ -0,0 +1,157 @@
# HEY overview video
Notes on `overview.mp4`, HEY's own two and a half minute "So how does HEY work?" walkthrough
(1920 by 1080, 148 seconds). The frames in `screenshots/hey-video/` were pulled from it at the
moments below. The narration is transcribed in full at the end because it is the tightest
statement of the workflow anywhere, and the order it presents things in is the order a new user
meets them.
## What the video shows, in order
**The Imbox** (0:06 to 0:20). A single centred column with a big serif-less "Imbox" title. Two
groups: New for you at the top, Previously seen below, each row an avatar, subject, sender plus
snippet, and a date. A mint pill in the top left reads "Screen 5 first-time senders". There is no
sidebar, no count, no unread badge.
![Imbox with New for you and Previously seen](screenshots/hey-video/imbox-new-and-previously-seen.png)
**The Screener** (0:22 to 0:33). A page titled The Screener with the line "The people below are
trying to email you for the first time. You get to decide if you want to hear from them." One row
per sender with a thumbs-up Yes and a thumbs-down No on the left. "Clear all" at the right.
![The Screener](screenshots/hey-video/screener.png)
**Reply Later** (0:36 to 0:50). Select rows by clicking their avatars, and a floating indigo
action bar appears with Reply Later, Set Aside, Mark Seen, Move to, Reply Together, Workflow.
After Reply Later the threads leave the list and stack in a pile at the bottom left; clicking the
pile fans it out with a "Go to Focus & Reply" button.
![Action bar after selecting rows](screenshots/hey-video/action-bar-reply-later-set-aside.png)
![The Reply Later pile fanned out](screenshots/hey-video/reply-later-pile.png)
**Focus & Reply** (0:50 to 0:55). A page that lines up every Reply Later thread with the latest
message on the left and a reply box on the right. "Send email" per item.
![Focus & Reply](screenshots/hey-video/focus-and-reply.png)
**Set Aside** (0:58 to 1:05). The full bulk action bar: Reply Later, Set Aside, Mark Seen, Reply
Together, Bubble Up Later, Label, Collection, Workflow, Merge, Ignore, Trash. Set Aside builds a
second pile at the bottom right.
![The full action bar](screenshots/hey-video/action-bar-full.png)
![The Set Aside pile](screenshots/hey-video/set-aside-pile.png)
**The HEY menu** (1:08 to 1:10 and 1:27). Pressing H opens a command-palette style overlay: a
"Type to go to a person, place, or label" field, six tiles for Imbox, The Feed, Paper Trail, Reply
Later, Set Aside and Bubble Up with their number keys, then Your: Drafts, Sent, Contacts, HEY
World, Clips, Snippets, Screened Out, All Files, Spam, Trash, Everything, then labels.
![The HEY menu as a jump list](screenshots/hey-video/hey-menu-jump.png)
![The HEY menu, full](screenshots/hey-video/hey-menu-full.png)
**The Feed** (1:11 to 1:17). Newsletters rendered already open in a scrolling column. The narration:
"send all your newsletters, then view them in a scrollable feed, even open them within it."
![The Feed](screenshots/hey-video/the-feed.png)
**The Paper Trail** (1:18 to 1:24). A flat list with brand avatars, subtitle "The place for
receipts, confirmations, and other transactional emails you receive." Opening one shows the
thread page with a bottom action bar: Reply Now, Reply Later, Set Aside, Bubble Up, More.
![The Paper Trail](screenshots/hey-video/paper-trail.png)
![A thread's bottom action bar](screenshots/hey-video/thread-bottom-actions.png)
**All Files** (1:30 to 1:35). A grid of every attachment with a filter by type: Images, PDFs,
Calendar invites, Documents, Spreadsheets, Presentations, Media, Zip files, and "sent by everyone"
to filter by person.
![All Files](screenshots/hey-video/all-files.png)
![All Files filtered by type](screenshots/hey-video/all-files-filter.png)
**Ignore, Bubble Up, Workflows, notifications** (1:36 to 1:55). Ignore drops you off a long thread
silently. Bubble Up schedules a thread to return to the top of the Imbox on a chosen day.
Workflows is a kanban board of threads. The thread options menu holds Mark Unseen, Send me push
notifications, Ignore this thread, Add a note to self, Print this thread.
![The sidebar menu with labels, collections and workflows](screenshots/hey-video/sidebar-menu.png)
![A Workflow board](screenshots/hey-video/workflow-kanban.png)
![Thread options](screenshots/hey-video/thread-options-menu.png)
**Clips, Rename, Notes, Read Together** (1:56 to 2:19). Select text in a message and "Save clip";
the Clips library lists every clip with its sender and thread. Click a subject to rename it for
yourself only ("people outside of HEY won't know about the new name"). Add a sticky note under an
Imbox row. Select several threads and Read Together puts them on one page.
![Selecting text to clip](screenshots/hey-video/clip-selection.png)
![The Clips library](screenshots/hey-video/clips-library.png)
![Renaming a thread](screenshots/hey-video/rename-thread.png)
![The action bar with its key hints](screenshots/hey-video/action-bar-with-keys.png)
![A sticky note under a row](screenshots/hey-video/sticky-note.png)
![Read Together](screenshots/hey-video/read-together.png)
**The closing list** (2:20). Email address forwarding, reply to multiple emails at once, private
notes to self, combine multiple threads on one page, share emails via a link, no spy tracking, HEY
for domains, start a blog.
![The closing feature list](screenshots/hey-video/feature-list.png)
## What to take from it
The video sells three things, and it sells them in this order for a reason: consent (the
Screener), separation (Imbox, Feed, Paper Trail), and workflow (Reply Later, Set Aside, Focus &
Reply). Everything after that is garnish. The two piles are the single strongest visual idea, and
the action bar printing its key letters is the cheapest way to teach a keyboard.
The video also shows what HEY does not have: no reading pane, no way to see a list and a thread at
once, and a lot of round trips between pages. That is the part we are not copying.
## Transcript
So how does HEY work? Well, let's jump in. First, you get a fresh start with a new email address
at hey.com. Once you log in, you're taken to your Imbox. And no, that's not a typo. Im stands for
important. You have the newest emails at the top, and everything you've already read below that.
Now, the first time anybody emails your HEY address, their email gets sent to the Screener, which
tells you how many people have emailed you for the first time. It's kind of like screening your
calls. If you don't want to hear from someone, hit no. And you never have to hear from them again.
If you do, hit yes.
Now back in your Imbox, let's say you want to respond to a few emails, but don't have time right
now. Just click them, then hit Reply Later. Now those emails are organised neatly at the bottom of
your Imbox. You can click on one to reply to it now, or go to Focus & Reply, where it lines them
all up for you to reply to on one page. So you can knock them out one by one, distraction free.
Now, sometimes you have an email you want to access quickly without digging for it. Again, just
click the email, but this time hit Set Aside. Now there's a different pile at the bottom of your
screen for whatever you need to access quickly.
In addition to your Imbox, you also have the Feed, which is where you can send all your
newsletters, then view them in a scrollable feed, even open them within it. You also have the
Paper Trail, which is where your transactions, receipts, and shipping notices go. You just don't
need to see them all the time.
And there's much more you can do in HEY. Need a file? Go to All Files to see all your files
without having to dig through any emails. You can even filter them by file type or person. Want to
get off a long email thread? Hit Ignore instead of asking someone to remove you. Don't want to
forget an email? Hit Bubble Up, then schedule it to shoot to the top of your Imbox for whatever day
you choose. Have a project with lots of steps? Use Workflows to organise emails on a kanban board.
Don't want to miss an important email? Turn on notifications for a single thread. Need to grab a
small detail later? Just clip it, then reference it in your Clips library instead of searching for
it in an email. Want to change a subject line? Just rename it without messing it up for anyone
else. Want to add a note on an email in your Imbox? Throw up a sticky note. Want to read emails
quickly? Read it all together and you can read them all at once on one screen.
And it also does all these things, which is a lot. So maybe just give it a whirl yourself and
unlock the magic of HEY at your own pace.
+568
View File
@@ -0,0 +1,568 @@
# HEY (hey.com) feature dossier
Research notes on HEY, the email and calendar service from 37signals, written as a reference for designing a calm, keyboard-first, Gmail-backed desktop client. Everything below was checked against hey.com, help.hey.com, 37signals' own writing and podcasts, and third-party reviews between the 2020 launch and September 2026. Where a claim could not be confirmed it is marked "unverified". Screenshots live in `screenshots/hey/` and are mostly frames from HEY's own product demo videos, plus a few images from the hey.com changelog (`/new/`) where those showed UI the videos did not.
## 1. Positioning
HEY launched on 15 June 2020 as a hosted email service, not an app on top of Gmail. Signing up gives you a new `@hey.com` address, and the service only works through HEY's own web app and native apps (web, Mac, Windows, Linux, iOS, Android, plus a PWA, a CLI and a terminal UI added in 2026). There is no IMAP or POP, you cannot read HEY mail in another client, and you cannot use HEY to check another account: the only inbound path from Gmail or iCloud is forwarding, and the only outbound path for another address is "send as" over SMTP or Google/Outlook OAuth. 37signals calls this "A Platform, Not a Client" and says the redesign was only possible because they own both ends.
The pitch, in HEY's own words, is "email as it should be": a set of opinions rather than a set of settings. The manifesto page (`/the-hey-way/`) lists fifteen principles. The ones that shape the product most:
- "No consent, no attention": nobody lands in your Imbox until you have said yes to them once in The Screener.
- "Get less email": reduce volume at the source (senders) and separate essential from secondary (three boxes) rather than helping you move faster through a pile.
- "Workflows, not workarounds": Reply Later, Set Aside and Bubble Up replace marking-as-unread and starring.
- "Convention over configuration" and "HI not AI": no rules engine, no regex filters, no algorithmic sorting. You tell HEY where a sender goes and it obeys.
- "Just say flow": no archive, no inbox zero. Read mail drops into Previously Seen and time pushes it down.
- "Surface, don't dig": attachments, clips and notes are pulled out to the top rather than buried in threads.
- "No bother": push notifications off by default, opted in per contact or per thread; no unread badges or counts anywhere ("Countless").
- "Counterintelligence": spy pixels stripped and reported; images proxied so your IP never leaks.
- "Pay with money, not privacy": flat fee, no ads, no data mining.
Jason Fried's framing in interviews is that "you cannot fix the email problem until you have control over who can email you", and that "spam is not the problem with email anymore"; the problem is legitimate but unwanted mail. DHH's framing is that inbox zero is "tyranny", because the archive button turned every email into an obligation.
Pricing and plans (September 2026):
| Plan | Price | Notes |
|---|---|---|
| HEY for You | $99/year (billed annually only) | One @hey.com address, HEY Calendar, 100 GB, HEY World blog, all features. 30-day free trial with no credit card; the trial is capped at 500 emails/day and excludes HEY World and public share links. |
| Ultra-short addresses | $349/year for 3-character, $999/year for 2-character | Vanity addresses only. |
| HEY for Families | $179/year total | Up to five separate @hey.com accounts under one payer. |
| HEY for Domains | $12/user/month, first user $10/month, billed monthly | Your own domain, no @hey.com address, no HEY World. Adds multi-user admin, Extensions (sales@, support@ group addresses that can deliver to several people or forward out), Shared Threads with private comments, shared Collections and Workflows, a Catch-all box, account managers. No free trial; you get 30 days to complete setup. |
If you pay for one year, the @hey.com address is yours forever and can forward out even after cancelling; trial-only addresses are recycled after 90 days. Export is MBOX for mail, vCard for contacts, .ics for calendars. Import of old mail is refused on principle ("a fresh start is a blessing, not a curse"); you import contacts (vCard) and forward new mail.
Sources: https://www.hey.com/, https://www.hey.com/the-hey-way/, https://www.hey.com/pricing/, https://www.hey.com/domains/, https://www.hey.com/faqs/, https://help.hey.com/article/754-can-we-import-our-old-emails, https://help.hey.com/article/719-can-i-use-hey-to-check-my-other-email-accounts, https://help.hey.com/article/882-what-can-i-do-on-the-free-trial, https://techcrunch.com/2020/07/06/oh-hey/, https://37signals.com/podcast/hey-whats-going-on/
## 2. The workflow, end to end
### A new sender arrives
Every inbound message first passes HEY's spam filter. If it survives and the sender has never emailed you before (and is not in your contacts), it does not reach any box. It lands in The Screener, and the Imbox shows a small mint pill in the top-left corner, "Screen 3 first-time senders". The Screener itself is a page with a row per sender: avatar, name, address, subject and a snippet, with two buttons on the left, a thumbs-up "Yes" and a thumbs-down "No". Clicking the row expands the message inline so you can read it (with recipients and timestamp) before deciding, and offers "Screen in to Imbox & Reply" for the case where you want to answer immediately.
"Yes" screens the sender in. The default destination is the Imbox, but the Yes button has a chevron that lets you pick The Feed or the Paper Trail instead, and that choice becomes the permanent delivery rule for that sender. "No" screens them out silently: nothing is sent back, the sender cannot tell, and all future mail from them goes to a Screened Out box that auto-deletes after 90 days. The decision is per sender, not per message, and can be reversed later from Screener History or the contact page (re-screening someone in reveals whatever they sent in the last 90 days). Contacts you add or import are pre-approved and skip the Screener. If the Screener piles up, "Clear all" punts everything without decisions.
![The Screener: one row per first-time sender with Yes and No](screenshots/hey/screener.png)
The Screener. Each row is a first-time sender; Yes has a dropdown for choosing which box they go to.
![Expanding a Screener row shows the message and offers Screen in and Reply](screenshots/hey/screener-preview.png)
Clicking a row expands the message inline with recipients and timestamp, plus a "Screen in to Imbox & Reply" shortcut.
### Where screened-in mail goes
Every approved sender has exactly one delivery destination, changeable at any time from their contact page ("Delivering to..."), and changing it moves their existing mail too:
- The Imbox (the "im" is for important) is for people and services you want to hear from now. It is the only box with a read/unread concept.
- The Feed is for newsletters, promotions and long reads. It renders every email already open in a single scrolling column, newest first, like a social feed. No unread state, no obligation; items are recycled after 90 days by default.
- The Paper Trail is for receipts, confirmations and notifications. A plain chronological list with no unread state, "a digital shoebox". Senders that flood you can be bundled into one row here or in the Imbox.
One rule overrides the sender setting: any message with In-Reply-To or References headers (a reply, including automated "reply" notifications from Basecamp or GitHub) goes to the Imbox so you never miss a reply. HEY's advice for noisy services is "Imbox, bundled".
![The Feed with a newsletter rendered open](screenshots/hey/the-feed.png)
The Feed. Every newsletter is already expanded; you scroll, and "See more" or "See less" toggles the long ones.
![The Paper Trail list](screenshots/hey/paper-trail.png)
The Paper Trail: receipts and transactional mail in a flat list with brand avatars and no unread dots.
![Contact delivery menu: Imbox, The Feed, Paper Trail, Screened Out, displayed separately or bundled](screenshots/hey/contact-delivery-menu.png)
The per-contact delivery menu. Every sender has one destination and an optional "Bundled up" display mode.
### Triage in the Imbox
The Imbox is a single centred column. New For You is at the top: threads you have not opened, each with an orange dot, avatar, subject in bold, sender plus snippet underneath, date on the right. Previously Seen sits below in a slightly tinted band: everything you have read or sent, newest first, pushed down by time. A thread jumps back into New For You whenever a new reply arrives and drops back to Previously Seen once you look at it. You can "Mark Seen" without opening (from the avatar action menu), hover the New For You heading for "Mark all as seen", or press "Power Through New" to see every unread message on one page with inline reply boxes and quick actions. There is no archive: HEY's help centre answer to "Can I archive emails?" is that read mail goes to Previously Seen, and if you do not want to see it, put Cover Art over it or set the contact to recycle.
At the bottom of the screen two piles are always visible: Reply Later on the left, Set Aside on the right, each drawn as a small stack of cards with the top item's subject and sender. Clicking a pile fans it out; Reply Later's fan ends in a "Go to Focus & Reply" button, Set Aside's in "View the Set Aside Board". Reply Later is for things you owe a response to, Set Aside for things you need to reference (tickets, itineraries, links). Neither uses a flag icon in the list; the thread physically moves out of the list into the pile.
Focus & Reply is a dedicated page listing every Reply Later thread, each with the latest message on the left and a reply box on the right. Sending removes the thread from the queue and leaves a green "Sent!" banner in its place; you can do five of twelve and come back.
Bubble Up is HEY's snooze: pick "Later today", "Tomorrow", "This weekend", "Next week", "Surprise me", "Pick a date" or "If no reply by...", and the thread leaves the list and reappears in a Bubbled Up section above New For You at that time. It stays there until you "Pop" it, and a "Bubble Up Now" option pins something to the top immediately.
Bulk selection is by clicking the circle avatar on each row (or `x` on the keyboard); an action menu pops up with Reply Later, Set Aside, Mark Unseen, Move to Paper Trail, Move to The Feed, File in label, Add a Note, Read Together, Reply Together, Merge, Ignore, Trash.
![Imbox with New For You and Previously Seen](screenshots/hey/imbox.png)
The Imbox: New For You on top, Previously Seen below. Note the absence of any count.
![Imbox with the Reply Later and Set Aside piles at the bottom](screenshots/hey/imbox-piles.png)
The two piles at the foot of the Imbox, and the "Screen N first-time senders" pill top left.
![Reply Later pile fanned out](screenshots/hey/reply-later.png)
The Reply Later pile fanned open, with the "Go to Focus & Reply" button.
![Set Aside pile fanned out](screenshots/hey/set-aside.png)
The Set Aside pile fanned open, with "View the Set Aside Board".
![Focus & Reply page](screenshots/hey/focus-and-reply.png)
Focus & Reply: each Reply Later thread with its own reply box; sent ones collapse to a "Sent!" line.
![Bubble Up menu on a thread](screenshots/hey/bubble-up.png)
Bubble Up options, and the thread action bar with its single-letter key hints (R, L, A, Z, M).
![Power Through New](screenshots/hey/power-through-new.png)
Power Through New: all unread mail on one page with inline replies and quick actions.
### Reading a thread
There is no reading pane. Opening a thread navigates to a full page: participant avatars and a large centred subject at the top, then each message as a white card (sender, recipient, date, body, attachments as thumbnails), and a floating action bar at the bottom with Reply Now, Reply Later, Set Aside, Bubble Up and More. Each message has its own "•••" for view original, print, download and report spam. The More menu holds thread-level actions: notification options (send me push notifications, ignore this thread), add a note to self, get a public link, start another thread, forward, label, move (including redeliver to another linked account), trash, add to Collection or Workflow, don't automatically recycle.
From here you can clip any selected text into a Clips Library, add private notes (text or files) between messages, rename the subject for yourself only, and merge another thread into this one. A spy-tracker banner at the top of the page tells you which vendor's pixel was stripped.
![A thread page with the bottom action bar](screenshots/hey/thread-view.png)
A thread page. Big centred subject, message cards, floating action bar with key hints.
### Sending
Compose opens as a full "New Message" sheet (or a separate window with Shift+W): To, CC/BCC, Subject, body, then a row of "Send email", "Save draft", a clock (Send Later), a paperclip and a formatting toggle. Replies open inline under the message. Reply-all is the default when there are multiple recipients; you switch to sender-only from the To field. Drafts autosave and can be minimised to a dock at the bottom of the screen. Send Later exists (arrow next to Send email, choose day and time; scheduled mail waits in Drafts). Undo Send exists (banner after sending, or `q`; enabled by default for accounts created after December 2022, toggle in Accounts & Settings). Signatures are replaced by Name Tags: 150 characters of text, bold/italic/link only, no images. Big attachments are sent as permanent download links with no download tracking. Special send variants exist for the piles: "Send & Mark Done" from Set Aside, "Now and Pop" from Bubble Up, and "Reply to Everyone" to send one reply to several selected threads.
![Compose window](screenshots/hey/compose.png)
New Message in its own window: To, CC/BCC, Subject, Send email, Save draft, Send Later clock, attach, format.
![Send Later picker](screenshots/hey/send-later.png)
Send Later: a day and time picker; scheduled emails are held in Drafts.
Sources: https://www.hey.com/how-it-works/, https://www.hey.com/flow/, https://help.hey.com/article/722-the-screener, https://help.hey.com/article/759-imbox, https://help.hey.com/article/840-why-are-these-notifications-going-to-my-imbox-instead-of-the-feed-paper-trail, https://help.hey.com/article/892-email-threads, https://help.hey.com/article/859-can-i-archive-emails, https://help.hey.com/article/820-how-do-i-undo-sending-a-message, https://help.hey.com/article/821-how-do-i-send-an-email-later, https://help.hey.com/article/807-reply-to-sender-or-reply-all
## 3. Feature catalogue
### The Screener
What it does: holds mail from any sender who has never emailed you (and is not in Contacts) until you say Yes or No. Runs after the spam filter. Yes defaults to Imbox; the Yes chevron chooses The Feed or Paper Trail. No is silent and permanent until you reverse it. Details and rules:
- Screener History (avatar menu, Accounts & Settings) lists everyone screened out and lets you screen them back in; doing so reveals their mail from the last 90 days.
- Screen out an existing contact from their page: Delivering To, then Screened Out.
- Domain screening: open a message, click the sender name, then the @domain button, and choose auto-screen in to Imbox, auto-screen out, or decide per sender. Large consumer domains such as gmail.com cannot be screened out as a whole.
- Imported or manually added contacts bypass the Screener.
- Speakeasy code: a secret word (key icon at the top right of the Screener) that, when it appears as a standalone word in a subject line, bypasses the Screener and marks the mail as special in the Imbox. Regenerate any time.
- HEY Spam Corps: opt in to get a third "Spam" button in the Screener; marking spam also screens out.
- "Clear all" empties the Screener without decisions. Screened Out and Spam are deleted after 90 days.
- Easter egg: Shift-click on No turns the thumbs-down into a middle finger (the "F*#k No" feature from the REWORK podcast).
- Forwarding out of HEY bypasses the Screener entirely.
Source: https://help.hey.com/article/722-the-screener, https://www.hey.com/features/the-screener/, https://help.hey.com/article/773-the-speakeasy-code, https://help.hey.com/article/889-spam-corps, https://37signals.com/podcast/the-f-k-no-feature/
### The Imbox
The only box with a read state. Two automatic groups, New For You and Previously Seen, plus a Bubbled Up group above them when anything is bubbled. Sent mail lands in Previously Seen. A new reply pulls a thread back to New For You. Actions: Mark Seen/Unseen from the avatar menu, Mark all as seen on the New For You heading, Power Through New, Read Together (link at the right of the New For You heading, or select rows). Bundles collapse all mail from one sender into a single row (Imbox and Paper Trail only; clicking a bundle with new mail shows all the new messages on one page). Cover Art can hide Previously Seen. There is no archive and no folders; labels are the closest thing.
![HEY menu](screenshots/hey/hey-menu.png)
The HEY menu, opened from the logo or `h`: a filter box, the six boxes as tiles with their number keys, labels, then Screened Out, Spam, Trash and Everything.
Source: https://help.hey.com/article/759-imbox, https://help.hey.com/article/784-different-states, https://help.hey.com/article/930-mark-all-as-seen, https://help.hey.com/article/765-bundle-emails
### The Feed
Newsletter reader. Each item is rendered expanded in a card with a large title, sender line and the full HTML; long items truncate with "See more..." and can be collapsed with "See less". Newest at top. No read state and no bulk unread count; HEY remembers "where you left off" and the HEY menu flags "new since you last visited". Actions (forward, move, label) come from the circle avatar on the item. Routing to the Feed is per sender only, never per domain. Feed mail is recycled after 90 days by default. Clips can be saved from Feed text (coupon codes are the stated use case). The Feed does not support bundles.
Source: https://help.hey.com/article/761-the-feed, https://www.hey.com/features/the-feed/, https://www.hey.com/new/
### The Paper Trail
Flat list for receipts and transactional mail. No read state. Supports bundles per sender. You cannot turn recycling on for the whole box, but per-contact recycling still applies to mail that lives there. Android can keep the last 30 days offline.
Source: https://help.hey.com/article/787-paper-trail, https://www.hey.com/features/paper-trail/
### Reply Later
Button in the thread action bar (`l`) or bulk menu. Moves the thread out of the list into the Reply Later pile at the bottom left; the pile fans open on click; a dedicated Reply Later box is reachable with `4`. Take a thread out by toggling the button again; it returns to the Imbox. There is no due date on Reply Later; Bubble Up's "If no reply by" covers that case.
Source: https://help.hey.com/article/774-reply-later, https://www.hey.com/features/reply-later/
### Set Aside
Button (`a`). Same pile mechanic on the bottom right, plus a Set Aside Board that previews every set-aside thread as a card on one screen. Inside the Set Aside box you can drag threads into named groups at the top ("bills to pay", "trip"). Remove with "Done" (`i` in the pile), or "Mark all as Done". Replying from Set Aside offers "Send & Mark Done".
Source: https://help.hey.com/article/777-set-aside, https://www.hey.com/features/set-aside/, https://www.hey.com/new/
### Focus & Reply
Page listing every Reply Later thread with a reply box beside each. Replying removes the item and shows a "Sent!" line; unanswered items stay for next time. Fried: "Focus and reply takes you out of that loop completely".
Source: https://help.hey.com/article/764-focus-and-reply, https://www.hey.com/features/reply-mode/
### Bubble Up
HEY's snooze (`z`). Presets: Later today, Tomorrow, This weekend, Next week, Surprise me, Pick a date, If no reply by..., and Bubble Up Now. Bubbled threads sit in a Bubbled Up section at the top of the Imbox until popped; a reply to a bubbled thread moves it to New For You; when replying you can choose "Now and Pop". Works from the bulk menu and from Power Through New. A Bubble Up box lists everything scheduled (`6`).
Source: https://help.hey.com/article/766-bubble-up, https://www.hey.com/new/
### Power Through New
Button on the New For You heading (`o`). Shows every unread thread on one page; reply inline, mark seen, Reply Later, Set Aside or Bubble Up per item; press `x` on an item for the full action menu; "Mark all as seen" at the bottom. Untouched items remain New For You.
Source: https://help.hey.com/article/923-power-through-new
### Read Together and Reply to Everyone
Select several Imbox rows and choose Read Together to open them all on one scrolling page (also the way to print several at once). Reply Together / Reply to Everyone sends one reply to every selected thread.
Source: https://help.hey.com/article/786-read-together, https://www.hey.com/features/reply-to-everyone/
### Clips
Select text in any message (Imbox or Feed), a "Save clip" button appears. Clips go to a Clips Library (HEY menu, Clips; `app.hey.com/clips`) showing the clipped text, sender and thread. Text only, any length, unlimited, synced across devices; deleting a clip does not touch the email; clipped threads are exempt from recycling.
![Clips Library](screenshots/hey/clips.png)
The Clips Library: highlighted passages with their sender and thread.
Source: https://help.hey.com/article/771-clips
### Collections
A named page that gathers several threads (with their own notes and a Recent Files strip) without merging them. Add from the thread's More menu or the bulk menu (`n`). Originally Domains-only, now on all accounts. Sharing a Collection with teammates is Domains-only; Collections can have notes and push notifications. Manual only, no auto-sorting.
![Collections](screenshots/hey/collections.png)
A Collection: three threads and their recent files on one page.
Source: https://help.hey.com/article/762-collections, https://help.hey.com/article/1006-collaboration-with-collections
### Cover Art
Picture icon at the top right of Previously Seen. Choose a preset or upload PNG/JPG/GIF (up to 40 MB, under 16K by 16K). The image slides over Previously Seen so the Imbox shows only new mail; tap to reveal. Stickies: a "+" at the top left of the cover adds yellow sticky notes (reminders, snippets, links) that live on the cover. Calendar cover art (web only) shows today and tomorrow, habits, "sometime this week" tasks, countdowns, and a join-call button 15 minutes before a meeting.
![Cover Art over Previously Seen](screenshots/hey/cover-art.png)
Cover Art hiding Previously Seen; the Imbox above it is empty ("Nothing new for you").
![Stickies on cover art](screenshots/hey/stickies.png)
Stickies pinned to the cover art.
Source: https://help.hey.com/article/781-cover-art
### Merge threads
Select two or more threads, Merge (`g`). A dialog asks "What should we call the new thread?" (prefilled from the longer thread) and lists the threads being merged. Permanent, with a confirmation; the other party is unaffected and their replies to either original thread arrive in your merged thread.
![Merge threads dialog](screenshots/hey/merge-threads.png)
The Merge dialog: name the result, confirm the list.
Source: https://help.hey.com/article/780-merge-threads
### Rename subject
Click the subject on a thread page. A popover asks "What would you like to call this?", notes that people outside HEY will not see the new name, and shows the original. The rename is yours only and persists no matter what others do.
![Rename subject popover](screenshots/hey/rename-subject.png)
Renaming a "no subject" thread for yourself.
Source: https://help.hey.com/article/783-rename-the-subject
### Sticky notes on Imbox rows
From the bulk menu, "Add a Note" (`y`). A yellow private note appears under the row in the Imbox only, not on the thread page.
![Sticky note under an Imbox row](screenshots/hey/inbox-notes.png)
A private sticky note attached to an Imbox row.
Source: https://help.hey.com/article/775-sticky-notes
### Notes to self (thread notes)
More menu, "Add a note to self". A blue block inside the thread, dated, with text and files (drag in, or paste images). Private; unlimited; can sit between messages. On HEY for Domains the same mechanism becomes private comments visible to teammates on a shared thread.
![Note to self inside a thread](screenshots/hey/thread-notes.png)
A private note between two messages in a thread.
Source: https://help.hey.com/article/782-note-to-self, https://www.hey.com/features/shared-threads/
### Workflows
Kanban boards. Create a workflow, add stages, drag threads between stages; the whole thread (replies, notes, files) travels with the card. Add a thread from its More menu. On Domains, workflows are shared with all users and Extensions can auto-add incoming mail to a workflow. HEY positions Workflows plus Contact Notes as its "light CRM".
![Workflow board](screenshots/hey/workflows.png)
A Workflow: stages as columns, threads as cards.
Source: https://help.hey.com/article/767-workflows, https://help.hey.com/article/929-crm
### Labels
Tags, not folders. Apply from the bulk menu (`b`) or a contact page ("Autofile in..." labels all new mail from that sender). Plus-addressing `[email protected]` auto-labels if the label exists and the sender is screened in. Labels appear as small pills on the row and as entries in the HEY menu.
Source: https://help.hey.com/article/884-labels
### Contact pages and contact notes
Click any avatar to reach the contact page: avatar, name, address, then a pill row: Not notifying / Delivering to Imbox / Autofile in... / Set up recycling / Add a note. Below that a rich-text Notes area, a Recent Files strip, and every thread with that person plus a Write button. Contacts list shows only approved (screened-in) contacts; unapproved still appear in search. Import vCard, export vCard, merge contacts by adding a second address, Contact Groups for addressing many people by one name.
![Per-contact notification choice](screenshots/hey/notifications-per-contact.png)
A contact page with the "When X emails you..." choice: Don't notify me or Send a push notification.
Source: https://help.hey.com/article/885-contacts, https://help.hey.com/article/770-contact-notes, https://help.hey.com/article/788-contact-groups
### Recycling
Off by default except The Feed (90 days). Per contact or per domain, choose 30 days, 90 days or 2 years measured from the last message in the thread; recycled mail goes to Trash and is deleted 30 days later. Longest applicable period wins. Clipped threads are exempt, and any thread can be set "Don't automatically recycle".
Source: https://help.hey.com/article/805-recycling-center
### Spy pixel blocking
HEY strips known tracking pixels and anything that looks like one (1x1 images, hidden trackers), names the vendor in a purple banner at the top of the thread ("You're protected. We blocked a spy tracker in this thread", expandable to explain what Hubspot or Mailchimp would have learned), and proxies all remaining images through HEY's servers so the sender never sees your IP. HEY claims about 98% coverage and publishes the list of blocked vendors. Outgoing HEY mail never contains trackers, and big-file download links are not tracked.
![Spy tracker banner](screenshots/hey/spy-tracker.png)
The spy tracker banner expanded, naming the vendor.
Source: https://www.hey.com/spy-trackers/, https://www.hey.com/features/spy-pixel-blocker/
### Ignore thread (mute) and unsubscribe
More menu, "Ignore this thread" (bulk `-`). Replies still arrive and are appended to the thread page, but the thread never returns to New For You; a yellow banner on the thread says "You ignored this thread" with "Stop ignoring". Deleting would not work because the next reply would resurrect the thread. HEY does not document any unsubscribe action; nothing on hey.com, in the changelog or in the help centre mentions one. The HEY answer to unwanted newsletters is to screen the sender out, route them to The Feed, or set them to recycle.
![Ignored thread banner](screenshots/hey/ignore-thread.png)
An ignored thread, with the "Stop ignoring" control.
Source: https://help.hey.com/article/769-ignore-a-thread, https://www.hey.com/features/mute-thread/
### Notifications
Push notifications are off by default everywhere. Turn them on per contact (contact page, "Send a push notification") or per thread (More menu, "Send me push notifications"), or per Collection. HEY refuses to show icon badges or unread counts "by design". Android notifications carry Mark Seen, Reply Later and Set Aside actions.
Source: https://help.hey.com/article/772-notifications, https://www.hey.com/features/notifications/
### Attachments, All Files, big files
All Files (HEY menu) is a library of every attachment ever received, filterable by type (images, PDFs, calendar invites, documents, spreadsheets, presentations, media, zip) and by sender at the same time, each card showing the thread it came from as a link. Signature junk (logos, social icons) is excluded. Sent Mail has a Recent Files strip of everything you have sent. Download all attachments in a thread with two clicks. Big attachments are sent as permanent direct-download links to any recipient, untracked.
![All Files](screenshots/hey/all-files.png)
All Files with the type filter open.
Source: https://help.hey.com/article/785-all-files, https://help.hey.com/article/768-sending-large-files
### Search
`s` or `/`. Results appear as you type (first seven), Cmd+Return or "View all results" opens the full page with a "Refine your results" rail: box (Imbox, The Feed, Paper Trail), also these words, none of these words, this exact phrase, from, to, subject, date, label, has attachment. Search covers Trash. Results support bulk actions. Mobile keeps a local search history. HEY says search became "up to 7x faster" in 2025, which is an admission that it used to be slow.
![Quick search](screenshots/hey/search.png)
Quick search dropdown as you type, with "View all results".
Source: https://help.hey.com/article/845-search, https://www.hey.com/new/
### Everything, Spam, Trash, Screened Out, Sent, Drafts
The HEY menu's "Other stuff" section: Screened Out (auto-deleted after 90 days), Spam (90 days), Trash (30 days), Everything (`app.hey.com/topics/everything`, every message across all boxes including spam and screened out). Sent Mail and Drafts are in the "Your" section. Drafts autosave; a draft can be minimised to a dock at the bottom of the window.
Source: https://help.hey.com/article/903-how-can-i-see-all-my-emails, https://help.hey.com/article/1014-empty-trash-spam-or-screened-out, https://help.hey.com/article/848-email-drafts
### Sorting and grouping in the Imbox
There are no sort options. Order within a group is strictly newest first. Grouping is fixed: Bubbled Up (if any), New For You, Previously Seen. The only other structure is bundles (per sender), labels (pills), and multi-account markers (a triangle for personal, a square for work when accounts of different types are linked).
Source: https://help.hey.com/article/784-different-states, https://www.hey.com/link-multiple-accounts/
### Signatures (Name Tags), Snippets, Autoresponder, forwarding, send-as
- Name Tag: a 150-character text-only signature (bold, italic, link) toggled in Edit Profile; one per address, including send-as addresses.
- Snippets: saved blocks of text or whole emails, inserted while composing (HEY menu, Snippets).
- Autoresponder: per account, with its own message; ignores forwarded mail, lists, bulk, auto-replies and spam; one auto-reply per contact per 7 days.
- Forwarding in: Accounts & Settings, Forwarding & Sending, Connect an address; then set forwarding at Gmail or iCloud. Replies to forwarded mail can go from the HEY address or the external one.
- Send as: SMTP with basic auth, or OAuth for Google and Outlook. Google Advanced Protection accounts cannot be used.
- Forwarding out: everything non-spam, bypassing the Screener. Redeliver: route a specific sender's mail from one linked account to another.
Source: https://help.hey.com/article/744-name-tags, https://help.hey.com/article/795-snippets, https://help.hey.com/article/776-autoresponder, https://help.hey.com/article/1055-forwarding, https://help.hey.com/article/733-sending-with-a-non-hey-email, https://help.hey.com/article/1013-redeliver
### Sharing: public links, Shared Threads, Extensions
Any thread can get a public read-only link (paid accounts only) that shows the whole thread and future replies on a HEY-formatted page; link holders cannot reply. On Domains, threads can be shared with teammates who then see everything including future replies, with private comments in blue blocks. Extensions are group addresses (sales@) delivered to several people or forwarded to a help desk, with a choice of whether new members see history.
Source: https://help.hey.com/article/779-shareable-links, https://www.hey.com/features/shared-threads/, https://help.hey.com/article/819-extensions
### HEY Calendar
Included since 2024. Views: Day (a single vertical timeline "telling the continuous story of your life"), Week, Year (all-day and multi-day events only). Extras: "Sometime this week" undated tasks that roll forward, Habits, Time tracking, Journal (private daily notes), countdowns, named days, day background photos, circled days, collapsed Nighttime hours, colour-coded sub-calendars, multiple reminders, multi-timezone, sharing with HEY users, ICS import and feeds, Google/Apple/Outlook import, create an event from an email (auto-linked back), calendar search. Cannot print. Keyboard: `0` toggles between mail and calendar.
![HEY Calendar week view](screenshots/hey/calendar-week.png)
Week view with the "Sometime this week" row of undated tasks at the bottom.
Source: https://www.hey.com/calendar/, https://help.hey.com/article/800-calendar-overview, https://help.hey.com/article/900-sometime-this-week, https://help.hey.com/article/837-calendar-day-features
### HEY World
Send an email to `[email protected]` (sole To recipient) from a paid HEY for You account and it is published at `world.hey.com/you/post-title`; readers subscribe by email or RSS; you can add a bio, pin posts, edit or delete posts, and export or import subscribers. Not available on trials or on HEY for Domains.
Source: https://www.hey.com/world/, https://help.hey.com/article/763-hey-world
### Mobile apps
iOS and Android apps for Email and Calendar (separate apps), described as full-featured. Layout is the same single column with the piles at the bottom; the HEY menu is reachable everywhere. Swipe left/right defaults to Seen/Unseen and is customisable to Reply Later or Set Aside. iOS has home screen widgets (New for you, The Feed, Paper Trail, Reply Later, Set Aside, Screener), Siri shortcuts, share sheet, iPad multi-window and keyboard navigation. Android has Material You calendar widgets, notification actions, device contacts, and offline Paper Trail. No app icon badges on either platform.
![Mobile app](screenshots/hey/mobile.png)
HEY on iOS: The Feed and the Imbox with the piles at the bottom.
Source: https://www.hey.com/apps/, https://help.hey.com/article/847-widgets, https://www.hey.com/new/
### Multi-account, security, CLI
Link any number of HEY accounts and see them merged in one Imbox (with type markers) or one at a time. Mandatory TOTP two-factor for paying customers, WebAuthn keys supported, no SMS. A dark-mode toggle independent of the OS. In 2026 HEY shipped a CLI (`curl -fsSL https://hey.com/install-cli | bash`), a TUI (`hey tui`), and an MCP server (`hey mcp`, with a `--read-only` flag) so agents such as Claude Code can screen mail, clear Reply Later and draft replies.
Source: https://www.hey.com/features/multi-account/, https://www.hey.com/features/security/, https://www.hey.com/agents/, https://help.hey.com/article/1189-using-ai-agents-with-hey
## 4. Keyboard shortcuts
The model is single, unmodified mnemonic keys: no two-key sequences ("g then i") and only a handful of modifier chords (Cmd/Ctrl+J for the menu, Cmd/Ctrl+Return to send, Shift+. and Shift+W). Keys are contextual: `o` is Power Through New on the Imbox page but Read Together in the bulk menu; `r` is Reply Now on a thread but Reply Together in bulk; `i` is Move to Imbox in bulk and "Done" inside the Set Aside pile. Press `?` anywhere (or the keyboard icon bottom right) for the list. The bottom action bar on a thread prints the letter next to each button.
Navigation (anywhere):
| Key | Action |
|---|---|
| 1 | Imbox |
| 2 | The Feed |
| 3 | Paper Trail |
| 4 | Reply Later |
| 5 | Set Aside |
| 6 | Bubble Up |
| 9 | Previously Seen |
| 0 | Toggle Calendar / Email |
| s or / | Search |
| h or Cmd/Ctrl+J | HEY menu |
| ? | Help and shortcut list |
Imbox page:
| Key | Action |
|---|---|
| w | Write a new email |
| Shift+W | Write in a new window (desktop browsers) |
| o | Power Through New |
| z | Bubbled Up |
On a thread ("message action shortcuts"):
| Key | Action |
|---|---|
| r | Reply Now |
| l | Move to Reply Later |
| a | Move to Set Aside |
| z | Bubble Up |
| f | Forward |
| b | Label |
| v | Move |
| t | Trash |
| m | More menu (shown on the action bar; the help page lists it for adding to a Collection) |
| u | Mark Unseen (from the More menu) |
| Shift+. (>) | Expand message previews ("See more") |
| Cmd/Ctrl+Return | Send |
| q | Undo send (immediately after sending) |
| Cmd/Ctrl+B, I, K | Bold, italic, link while composing |
Bulk actions (Imbox, The Feed, Paper Trail, Set Aside, Reply Later, Bubble Up lists):
| Key | Action |
|---|---|
| x | Select row |
| j / down | Next row |
| k / up | Previous row |
| Enter | Open thread |
| ; | Focus the bulk actions menu |
| l | Move to Reply Later |
| a | Move to Set Aside |
| z | Bubble Up |
| u | Mark Unseen |
| e | Mark Seen (listed in the help centre article, not on the marketing page) |
| i | Move to Imbox (also "Done" inside the Set Aside pile) |
| p | Move to Paper Trail |
| d | Move to The Feed |
| o | Read Together |
| r | Reply Together |
| b | Add to label |
| n | Add to Collection |
| y | Add a sticky |
| g | Merge |
| - | Ignore |
| t | Trash |
Calendar:
| Key | Action |
|---|---|
| 0 | Back to Email |
| t | Today |
| d | Day view |
| w | This week |
| u | Week view |
| y | Year view |
| n | New event |
| s or / | Search |
| b | Habits |
| l | Time tracking |
| j | Journal |
| k | Write in Journal (Day view) |
| left / right | Previous / next day |
Pop-up menus: Enter or Space opens, up/down move, Esc clears typed text then closes on a second press, Tab closes and moves focus on.
Sources: https://www.hey.com/keyboard-shortcuts/, https://help.hey.com/article/758-keyboard-shortcuts, https://www.hey.com/new/ (Shift+W)
## 5. Interaction and UI design notes
Layout. One centred column of roughly 900px on a pale grey page, holding a white "sheet" with rounded corners and a soft shadow. Chrome is minimal: Search at top left, the HEY hand logo with a chevron in the centre (this opens the HEY menu), your avatar at top right, a "< Imbox" back pill when you are inside a page. There is no sidebar and no reading pane: the list is a page, the thread is a page, the contact is a page, and you navigate between them. The HEY menu is the only global navigation and behaves like a command palette: a "Type to go to a person, place, or label..." field over six large tiles (Imbox, The Feed, Paper Trail, Reply Later, Set Aside, Bubble Up) with their number keys printed in the corner, then labels, then Screened Out, Spam, Trash, Everything.
Page headers. Every page has a big centred title ("Imbox", "The Screener", "Paper Trail", "Focus & Reply") flanked by thin rules, and a one-line grey subtitle explaining the page ("The place for receipts, confirmations, and other transactional emails you receive."). Section headers inside a page are small uppercase labels with a rule ("NEW FOR YOU", "PREVIOUSLY SEEN", "WANT TO GET EMAILS FROM THEM?").
Rows. A row is: an orange dot for unread (Imbox only), a circular avatar (photo, brand logo, or two or three initials on a saturated colour), subject in black semibold, then on the second line the sender name, a short separator, then the snippet, all in grey, and the date or time right-aligned in grey. Group threads show a cluster of tiny avatars after the subject. Attachments show a paperclip glyph after the subject, labels show as small outlined pills. Rows are about 40px tall and there is no density option. Selecting a row is done by clicking the avatar, which turns into a checked circle, and the bulk menu slides in as a floating indigo panel.
The two piles. At the foot of the Imbox, two stacks of two or three overlapping white cards, each with an icon (a reply-clock for Reply Later, a pin for Set Aside), the top card showing subject and sender. Click to fan them up into a vertical stack of small cards ending in a button. It is a literal desk metaphor and the strongest single visual idea in the product.
Thread page. Participant avatars above a large centred subject; each message a white card with sender name in bold, address in grey, "to" line, date and "•••" at right; attachments as thumbnails with file name and size; quoted text collapsed behind a chevron. A floating pill-shaped action bar sits at the bottom centre with five icon-and-label buttons and their key letters. Notes to self are blue blocks; the spy tracker warning is a purple banner at the top.
Compose. New mail opens as a modal sheet (or its own window). Reply is an inline box under the message; in Focus & Reply the reply box sits to the right of the message. Buttons are pills: "Send email" filled indigo, "Save draft" outlined. Formatting is behind a toggle; there is a colour tool and code blocks but no markdown (a reviewer complaint).
Colour and type. White surfaces, #f5-ish grey page, HEY indigo/purple (around #5522fa) for primary actions and menus, mint/teal pills for status ("Screen 5 first-time senders", "Send email" in Focus & Reply, "Done"), orange for unread dots, yellow for stickies, saturated avatar colours. Menus and popovers are solid indigo-to-purple gradients with white text, which reads as playful rather than corporate. Type is the system sans (SF on Apple), with heavy weight for page titles and a fairly small body size in lists.
Illustration and voice. Hand-drawn blue arrows and squiggles in marketing and onboarding, the waving-hand logo, sparkles on the empty state ("Nothing new for you."), a keyboard glyph in the bottom corner for shortcuts. Copy is first person and cheeky ("Everyone else can put stuff in your inbox, but you can't. With HEY you can."). Reviewers are split: some call the oversized "Imbox" wordmark "corny".
Empty states. The Imbox with nothing new shows a small sparkle and "Nothing new for you." with Previously Seen (or cover art) below. Screened Out and Spam pages explain their retention ("These will be automatically deleted after 90 days") with an "Empty" button.
Feed rendering. Each newsletter is a card with a big bold title (the subject), sender and address, then the HTML email at full width inside the card, truncated after roughly one screen with "See more...", and "See less..." once expanded. There are no row previews in the Feed: the preview is the email.
Mobile. Same structure squeezed to one column: title, New For You, rows with avatar and two-line text, piles fixed at the bottom, a floating hand button for the HEY menu. Notifications and badges are absent unless opted in. Dark mode exists on all platforms but email bodies stay light (a common complaint).
Sources: the screenshots in this folder, https://www.hey.com/how-it-works/, https://www.hey.com/features/, https://hulry.com/hey-email-review/, https://danielcassman.com/posts/2020/08/29/hey-email/
## 6. What people praise and what they criticise
Praise:
- The Screener is the feature people name first. TapSmart: the reviewer's "favorite feature by far". Hulry: "Instead of reacting to spammy senders, I am proactively blocking them out."
- The Feed lets people batch-read newsletters two or three times a day instead of one at a time.
- Paper Trail and All Files: receipts out of the way, attachments findable without finding the email first.
- Reply Later and Set Aside are described as "separation of concerns"; Hulry: "HEY is all about workflows, not workarounds."
- Silence by default and the absence of unread counts genuinely lower anxiety for people who stick with it.
- Privacy: tracker blocking is visible and on by default, and the flat fee removes the incentive to mine data.
- Design: "Email is fun again" (Hulry); "the best email product I've ever used" (Daniel Cassman); Agentys says the $99 "deserves credit" given the calendar, storage and apps included.
Criticism:
- Price: $99/year, annual only, no monthly option for personal accounts.
- A new address and lock-in: you must move to @hey.com (or pay for Domains), forwarding leaves your old provider still reading your mail ("Paying for email with both data and money", Hulry), and nothing but forwarding survives if you leave. Corporate users on company domains are out.
- No IMAP/POP, no third-party clients, no checking other accounts, no import of history. TechCrunch called it "the literal opposite of an MVP" and noted the deliberate lock-in.
- It is entirely manual. Every new sender is a decision, and routing is per sender only: you cannot route by subject or by domain to the Feed, so form responses and automated mail are "either in or out". Agentys: "HEY makes that time feel more intentional. It does not reduce it."
- The Screener still gets a trickle of spam (one reviewer: one to five a day), and the spam filter has false positives on bank mail.
- No reading pane and no dense list: each thread is a page, list rows are truncated, and read mail sliding into Previously Seen frustrates people who want a stable list to work through.
- Search was widely criticised as weak into early 2025; HEY's own "7x faster" and "Refine your results" changelog entries confirm it needed work.
- Editor: no markdown, formatting behind buttons, partial dark mode (email bodies stay light).
- Rigidity: "you can't pick and choose the bits you like" (TapSmart). The spy-tracker banner is "distracting" with no way to hide it (Cassman).
- No AI features at all, a plus for some and a minus in 2026 comparisons (Agentys, MailOver).
- Trust: reports of missing mail with support blaming the user (Phil Reynolds), no end-to-end encryption, and some users leaving over 37signals leadership controversies.
- Early complaint about no multi-account support was fixed by account linking; early complaint about no custom domains was fixed by HEY for Domains.
Sources: https://www.tapsmart.com/apps/hey-email-review/, https://hulry.com/hey-email-review/, https://www.agentys.io/en/blog/is-hey-email-worth-it, https://justinharter.com/a-new-review-of-hey-email-in-2024-and-how-its-changed-my-processes/, https://danielcassman.com/posts/2020/08/29/hey-email/, https://techcrunch.com/2020/06/16/basecamp-launches-hey-a-hosted-email-service-for-neat-freaks, https://philreynolds.dev/posts/2023/bye-to-hey, https://mailover.ai/blog/best-hey-email-alternatives.html, https://en.wikipedia.org/wiki/Hey_(email_service)
## 7. Takeaways for a new client
Steal the model, not the branding. HEY's real invention is that a sender has exactly one destination and the client owns that decision, not the sender; on Gmail that maps cleanly to a "first-time sender" query (no prior thread with that address, not in Contacts) held under a `Screener` label and three destination labels that a client-side rule applies on arrival. Gmail filters can do the steady-state routing once you have decided, so the Screener only has to be a client feature for the first message.
Three boxes are right, two groups are right. Imbox / Feed / Paper Trail beats Gmail's five tabs because the user chose every placement and there is no algorithm to second-guess. New For You over Previously Seen is a better default than unread-mixed-with-read, and it costs nothing to implement on top of Gmail's UNREAD label. Skip Gmail's Important marker entirely.
Reply Later and Set Aside as physical piles are worth copying, including the fact that a thread leaves the list when you pile it. Focus & Reply is the payoff and is trivial once the pile exists. Bubble Up is just snooze, but "If no reply by" is a good addition and the Bubbled Up section at the top is better than Gmail's snoozed-mail-reappears-as-unread.
Keep HEY's keyboard grammar: numbers for boxes, single mnemonic letters, letters printed on the action bar, `x` to select, `;` for the bulk menu. Drop the contextual reuse (`o`, `r`, `i` meaning different things in different places); a keyboard-first client should not do that.
Per-thread and per-contact notification opt-in, no badges, and no counts are cheap and are the single biggest calm-ness win. Do them.
Rename-for-me, merge, private notes, clips and cover art all depend on owning the data model. On Gmail you can only fake them with client-side metadata that other clients will not see; rename and notes are worth that, merge probably is not (it is permanent and Gmail threading will fight you), cover art is a gimmick.
Do not copy the no-reading-pane, one-thread-per-page layout wholesale. It is the most consistent complaint, and a desktop client has the width. A collapsible reading pane, or a single column with a keyboard-driven inline expand, keeps the calm without the round trips.
Do not copy "no archive". HEY can afford it because it controls storage and retention; on Gmail, archive is how the user's other clients stay sane, so keep `e` as archive and treat Previously Seen as a view, not a policy.
Do not copy per-sender-only routing. The lack of subject or domain rules is HEY's most repeated functional complaint; let the Screener decision also offer "everyone at this domain" and let a rule match on List-Unsubscribe or Precedence headers to pre-suggest Feed and Paper Trail.
Spy pixel blocking is achievable on Gmail (proxy or strip remote images, keep a vendor list, show the banner) and is a differentiator worth the effort.
HEY's weakest parts are search and the editor; both are places where a Gmail-backed client gets to inherit Gmail's strengths. Lean on them.
+310
View File
@@ -0,0 +1,310 @@
# Email client landscape
A survey of the clients around the product we are designing: a calm, keyboard-first, Gmail-backed native desktop client, macOS first, then Linux, then phones. HEY and Superhuman are covered in their own documents; they appear here only in the comparison table. Everything below is as of early September 2026. Prices are USD. Anything we could not confirm from a primary page is marked "unverified" inline.
The short version: Mimestream is the only shipping native client built directly on the Gmail API and it proves the model works, but it copies Apple Mail's layout and has spent six years unable to ship scheduled send or a real snooze because it refuses to run a server. Everyone else either went web-first and AI-first (Shortwave, Notion Mail, Zero), or relays your credentials through their own servers (Spark), or is a hosted mail service rather than a client (Fastmail, Proton). Nobody has shipped a quiet, dense, keyboard-driven native Gmail client with HEY-style sender screening. That is the gap.
## Mimestream
Mimestream is a Swift, AppKit plus SwiftUI macOS client that talks only to the Gmail API. Neil Jhaveri, previously on Apple's Mail team, started it in 2019; the public beta ran from September 2020 and 1.0 shipped on 22 May 2023. The company is five people, bootstrapped, and the current release is 1.10.6 (29 July 2026). It costs $4.99 a month or $49.99 a year after a 14-day trial with no card, covers every Google account you own on up to five devices, and is subscription only: the app stops working when the subscription lapses, which is the single loudest complaint on Hacker News. It requires macOS 12 or later and is distributed directly, not through the App Store. An iOS build exists only as a TestFlight beta (open beta since July 2026, iOS 26 required) with no App Store date; one reviewer guesses autumn 2026, unverified.
The Gmail mapping is the most faithful in the field. Labels are real Gmail labels: many per conversation, nested, colours and sidebar visibility synced both ways, drag to re-parent, shown as coloured chips in the list and on drafts. Gmail's categories (Primary, Social, Promotions, Updates, Forums) appear as separate inboxes in the sidebar, per account, with an honest caveat in the help text: there is no Gmail API for the category settings, so the app can silently disagree with Gmail about which tabs are on. The Important marker is an opt-in overlay, `is:important` works in search, and importance is a filter criterion. Server-side filters are editable in-app, as are the vacation responder and aliases. Accounts are grouped into Profiles (Personal, Work) shown as tiles at the top of the sidebar; each profile is a unified inbox of its accounts, and 1.10 added an optional "All" profile plus per-profile notification schedules and Focus Filter support.
Keyboard support comes as three switchable sets: Mimestream's own, Apple Mail, and Gmail. The Gmail set is single-key (`j`/`k`, `e`, `c`, `r`, `/`, `g` then `i` or `l`, `z`, `b` for snooze, `#` for trash); the others are Cmd chords. "Go to Folder" (Shift-Cmd-O) is a fuzzy jump across labels. There is no command palette and no per-shortcut customisation, and reviewers ask for both. Sync is delta sync on the Gmail history API, and push arrives through "Private Push": the app registers `users.watch` against Mimestream's Pub/Sub topic, Google publishes only the email address and a historyId, Mimestream's relay forwards that to the Mac (APNs on iOS), and the app fetches the changes itself. The relay never holds OAuth tokens. On macOS before 26 it falls back to IMAP IDLE. It is deliberately a "streaming" client: only a sliding window of recent mail per label is cached, older mail is reachable through server search, and offline actions are queued. Search is Gmail's server search (every operator works) merged with a local From and Subject prefix index. Reviewers consistently call it the fastest-syncing client they have used; one measured a six-minute initial sync for 46k messages across three accounts and sub-1.5-second searches.
The limits are structural rather than accidental. No scheduled send, because the Gmail API cannot do it and the team will not run a service with account access (a design for an opt-in service using only the `gmail.send` scope has sat on the roadmap for years, 533 votes, still "Considering" as of August 2026). Snooze is a Labs feature that only hides the thread locally; the message stays in Gmail's inbox for every other client because Mimestream cannot guarantee it will be running to unsnooze. No mute (no API). No Priority Inbox sections, no bundles or custom splits, no plugins, no AppleScript, no Windows or Linux, no non-Google accounts (IMAP has 937 votes). On the API itself the founder has been consistent since 2020: it "requires multiple round trips with the server to perform some basic tasks that IMAP can execute in 1 round trip", the throttling "is fine for a very thin client like Mimestream, but would not be a good fit for a client that wanted to download every message", and "for a thick email client, the Gmail API has definite efficiency issues compared to IMAP". The changelog backs this up: per-second quota errors on accounts with many labels, Gmail enforcing a 100-request batch cap, concurrent-request limits, draft sync reworked to cut quota, and at least three releases that exist only because Google changed API behaviour without notice. Mimestream also pays for a CASA Tier 2 assessment every year.
Visually it is Apple Mail with better Gmail plumbing: a stock three-pane window, unified toolbar, bold sender with thread count, subject, one grey preview line, coloured label chips, attachment chips with file-type icons, round avatars in the reading pane only, system font throughout, Liquid Glass on macOS 26. A long-time beta user's verdict on HN was that the additions over Gmail "are mainly cosmetic or nice-to-haves ... it's mainly a pretty native app UI". That is unfair to the sync work and fair about the design.
![Mimestream main window](screenshots/landscape/mimestream-inbox.png)
*Mimestream 1.10: profile tiles, category inboxes and Favorites in the sidebar, label and attachment chips in the list, threaded reading pane.*
![Mimestream category inboxes](screenshots/landscape/mimestream-categories.png)
*Gmail's five categories rendered as separate sidebar inboxes, with Promotions selected.*
![Mimestream compose window](screenshots/landscape/mimestream-compose.png)
*Compose: recipient tokens, From with avatar, and an @mention rendered inline.*
Worth stealing:
- Labels as first-class Gmail labels with two-way colour and visibility sync, and categories as views over `CATEGORY_*` labels.
- Three shortcut sets with a Gmail single-key mode; fuzzy Go to Folder.
- Profiles: grouped accounts, each a unified inbox, an "All" escape hatch, per-profile quiet hours.
- Private Push: `users.watch` plus a relay that only ever sees a historyId, tokens never leave the device.
- Streaming sync with per-label windows, server search merged with a small local index, offline action queue.
- Tracking-pixel blocking on by default for a maintained list of services.
Not for us:
- The Apple Mail layout and list density; a calm client needs a quieter row than bold sender, preview, coloured chips and attachment chips.
- Client-only snooze that lies to every other Gmail client.
- Shipping without scheduled send for six years; a tiny opt-in `gmail.send` service is the answer and they know it.
- No command palette, no shortcut customisation.
Sources: https://mimestream.com, https://mimestream.com/pricing, https://mimestream.com/faqs, https://mimestream.com/releases, https://mimestream.com/blog/1.10-released, https://mimestream.com/blog/casa-verified, https://mimestream.com/trust/private-push, https://mimestream.com/help/user-guide/inbox-categories, https://mimestream.com/help/user-guide/keyboard-shortcuts, https://mimestream.com/help/user-guide/snoozing, https://mimestream.com/ios-beta, https://portal.productboard.com/mimestream/1-mimestream-roadmap, https://news.ycombinator.com/item?id=24422434, https://news.ycombinator.com/item?id=24423314, https://news.ycombinator.com/item?id=36033184, https://twitter.com/neil_jhaveri/status/1355787069155631105, https://www.macstories.net/reviews/mimestream-the-perfect-email-app-for-gmail-users-on-the-mac/, https://sixcolors.com/post/2021/10/mimestream-a-native-mac-app-with-proper-gmail-support/, https://thesweetsetup.com/mimestream-is-a-great-reliable-gmail-app-for-the-mac/, https://www.cultofmac.com/reviews/mimestream-mac-mail-app-review, https://email-tools.me/posts/mimestream-review/, https://techcrunch.com/2023/05/23/former-apple-engineers-mimestream-app-is-a-nifty-gmail-client-for-mac/
## Shortwave
Shortwave is a Gmail API client (Gmail and Workspace only; Microsoft has "no timeline") delivered as a web app with macOS and Windows desktop wrappers and iOS and Android apps. Its founders came from Google (the Firebase team) and the product reads as Inbox by Gmail's bundles and pins rebuilt around an AI assistant. Pricing is per seat per month with no free tier: Business $30, Premier $45, Max $120 (annual $24, $36, $100), after a 14-day trial. The tiers differ only in AI quota (model, requests per day, number of "AI filters"), which tells you what the company thinks it is selling. Several reviews still list a free tier and an $18 Pro plan; those are stale. The founders' attention has visibly moved to a separate product, Tasklet, which the homepage now leads with.
The triage model is the strongest part and is documented as "The Shortwave Method": the inbox is a list you clear. Done (`D` or `E`) archives and auto-advances, Snooze (`B` or `H`) handles date-bound items, Star (`S`) drops a thread into a Starred section pinned at the top of the list, and Todo (`T`) turns one or more threads into a named task in a Todos tab. Pin was removed in 2024 in favour of stars and todos. The list itself is sectioned Todos, Starred, Last 7 days. Bundles are not automatic classification: you enable them, per label (built-in Newsletters, Promotions, Travel, Updates, or any custom label) or per contact, and a bundle collapses to one compact row with a count and sender avatars that expands in place; one keystroke marks the whole bundle done. Splits are tabs across the top of the inbox, each defined by Gmail importance, labels, senders or an arbitrary search query, with drag-and-drop between them and bundling toggleable per split.
Layout is a setting: Default, Side panel (list and thread side by side) or Fullscreen (one thread at a time). Shortcuts are Gmail-style single keys plus a Cmd-K command palette and Cmd-J for the assistant. Undo send is server-side (10 seconds by default, the message still goes out if you close the app) and scheduled send takes natural-language times. On the receiving side it proxies images and blocks tracking pixels with an indicator; on the sending side it sells read statuses, link click tracking and a "recent opens" feed, which is the same tracking it blocks for you. Multiple accounts switch with Ctrl-1/2/3 but there is no unified inbox; the official workaround is Gmail forwarding. The AI layer is everywhere: a right-hand assistant sidebar, natural-language search, a one-line summary on every thread, Ghostwriter drafts trained on your sent mail, plain-English "AI filters" that label, star or archive, and MCP integrations.
Criticism from reviewers is consistent: Gmail-only is a hard wall, the price has roughly doubled since launch, the per-day AI caps on the cheapest tier feel restrictive, and your mailbox is indexed on Shortwave's servers and sent to third-party models, which one XDA reviewer called a dealbreaker for drafting replies. Nobody complains about speed.
![Shortwave desktop app](screenshots/landscape/shortwave-inbox.png)
*Shortwave: split tabs (Important, Support, Other, Todos) across the top, list sectioned Todos, Starred, Last 7 days, thread in a side panel with an AI summary line, assistant in the right sidebar.*
![Shortwave bundle row](screenshots/landscape/shortwave-bundles.png)
*A "Newsletters 68" bundle collapsed to one row with sender avatars; the tooltip shows a single keystroke marks all 68 done.*
![Shortwave AI bulk action](screenshots/landscape/shortwave-ai-mark-done.png)
*The assistant proposing to mark seven threads done. This is how much of the product surfaces: as a dialogue rather than a keystroke.*
Worth stealing:
- Done, Snooze, Star, Todo as a single-key vocabulary with auto-advance; Starred section pinned at the top of the list.
- Bundle rows: one collapsed line per label or sender with a count, expand in place, whole bundle actioned with one key. On Gmail the labels already exist.
- Splits as tabs defined by label, sender or query; per-split bundling toggle.
- Server-side undo send that fires with the app closed; layout as a plain three-way setting.
Not for us:
- The AI-first frame: the sidebar, per-thread summaries, autocomplete and AI filters are why the cheapest seat is $24 and why reviewers raise privacy.
- Read statuses and link tracking on send.
- "Multi-account" without a unified inbox.
- Web app in a desktop wrapper.
Sources: https://www.shortwave.com, https://www.shortwave.com/pricing, https://www.shortwave.com/features/, https://www.shortwave.com/docs/guides/method/, https://www.shortwave.com/docs/guides/bundles/, https://www.shortwave.com/blog/split-email-inbox-by-importance/, https://www.shortwave.com/blog/todos-and-stars/, https://www.shortwave.com/docs/references/shortcuts/, https://www.shortwave.com/docs/guides/customize-your-shortwave-settings/, https://www.shortwave.com/blog/announcing-scheduled-send-and-undo-send/, https://www.shortwave.com/blog/read-statuses-email-tracking/, https://www.shortwave.com/docs/how-tos/unified-universal-inbox-support/, https://www.shortwave.com/docs/how-tos/microsoft-outlook-exchange-other-sign-in-support/, https://email-tools.me/posts/shortwave-review/, https://cmdk.email/post/shortwave-review/, https://www.xda-developers.com/replaced-gmail-with-an-email-app-that-finally-makes-inbox-zero-feel-realistic/, https://9to5google.com/2024/06/05/shortwave-adds-inbox-splits/
## Notion Mail
Notion Mail is being shut down on 22 September 2026, announced 25 June 2026, seventeen months after its 15 April 2025 launch. Notion's stated reason is that "more than half of Notion Mail users manage emails without ever opening their inbox", so they are "going all in on using agents to run your inbox". Users can export drafts, scheduled mail, snippets and auto-label instructions; views and reminders are lost; the mail itself was always in Gmail. The product page already redirects. It is still worth studying because the "views" idea was the one genuinely new inbox primitive of the last two years, and because its footprint (Gmail only, macOS plus web plus a thin iOS app, Windows "soon" for its whole life, never Android, never a unified inbox) is a warning.
It was Gmail and Workspace only. The client was free with any Notion account, but the headline features (auto-label, AI drafting, AI-configured views) needed paid Notion AI, in practice the Business plan at $20 per seat a month annual ($24 monthly), which is where the "free but not really" reviews came from. One review also reports mail retention caps of 7 days on Free and 30 days on Plus, unverified.
Views live in a "Views" section of the left sidebar, as entries rather than tabs. Each view is a saved combination of filters, groups and properties, deliberately modelled on a Notion database view. Filters cover unread, read, attachment, calendar event, from, to, cc, bcc, subject, date and `label:` (nested labels unsupported). Group by date, starred, important, sender or domain, priority, label or unread, and the groups render as inline section headers in the list. Properties are user-defined columns on emails, for example a Select "Owner". A new view can be described in natural language (AI configures it), built manually, or picked from a template; default views were generated from your existing Gmail labels. Views only filter; they never label anything. Auto-label is a separate feature: a button top-right of the inbox where you type a plain-English instruction, it labels incoming mail, and it learns from you accepting or crossing out its suggestions. Everything else is competent and conventional: snippets with slash shortcuts and `{{variables}}`, meeting scheduling links through Notion Calendar, "all Gmail shortcuts" (`j`/`k`, `e`, `h` for snooze which it calls Set reminder, `l`, `#`, `z`, Ctrl-1 to 9 for accounts) plus a Cmd-K command menu, scheduled send with an explicit timezone, and a thread-style setting of side peek, centre peek or full page. Remote images were proxied but tracking pixels were not blocked; the help page said "we hope to block read receipts in the near future". Undo send was never documented.
Reviewers called it a pretty wrapper with shallow Notion integration (you could @-mention a page but not turn an email into a task or a database row), said view setup was manual and slow with no useful presets, found the AI drafts generic, and listed the platform gaps. The iOS app lacked snippets, scheduling, AI, schedule send and any view editing. Then the company killed it.
![Notion Mail inbox grouped by label](screenshots/landscape/notionmail-inbox.png)
*Inbox grouped by label (Hiring, Support, Travel headers), Views list in the sidebar, Auto label button top right.*
![Notion Mail Travel view](screenshots/landscape/notionmail-views.png)
*A "Travel" view grouped by trip; each group is an inline header, the sidebar lists views above mail folders.*
![Notion Mail edit view popover](screenshots/landscape/notionmail-editview.png)
*The "Edit view" popover: Group, Filter, Properties and hover actions, the Notion database vocabulary applied to mail.*
Worth stealing:
- Views as saved filter-plus-group definitions in the sidebar, with group headers inline in the list. Grouping by label, sender domain or date calms an inbox without any AI.
- Thread-style toggle (side peek, centre peek, full page).
- Gmail shortcuts by default plus a Cmd-K menu that doubles as the shortcut reference.
- Snippets with slash shortcuts and variables; scheduled send with a visible timezone.
Not for us:
- AI auto-label as the organising primitive; it needed a $20 seat and is exactly what is being deleted.
- Properties and columns on emails; the database metaphor never connected to anything.
- The footprint: Gmail-only, Mac plus web, no unified inbox, a mobile app missing half the features.
Sources: https://www.notion.com/help/notion-mail-inbox-is-going-away-what-to-do-next, https://www.notion.com/blog/introducing-notion-mail, https://www.notion.com/help/get-started-with-notion-mail, https://www.notion.com/help/views-groups-filters-and-properties, https://www.notion.com/help/navigate-your-inbox, https://www.notion.com/help/guides/organize-your-inbox-with-notion-ai-auto-labeling, https://www.notion.com/help/notion-mail-keyboard-shortcuts, https://www.notion.com/help/notion-mail-security-practices, https://www.notion.com/help/notion-mail-for-mobile, https://www.notion.com/pricing, https://www.androidauthority.com/notion-mail-is-shutting-down-3681674/, https://www.theregister.com/ai-and-ml/2026/06/26/notion-kills-its-gmail-client-after-ai-agents-keep-humans-from-troubling-inbox/5263024, https://efficient.app/apps/notion-mail, https://clean.email/blog/email-clients/notion-mail-review, https://www.eesel.ai/blog/notion-mail-reviews, https://matthiasfrank.de/en/notion-features/notion-mail/
## Spark
Spark, by Readdle, is the mass-market "smart inbox" client: Gmail, iCloud, Exchange, Outlook, Yahoo and generic IMAP on macOS, Windows, iOS, iPadOS and Android, no web client. Gmail connects through Google OAuth; whether the transport is the Gmail API or IMAP with XOAUTH2 is not documented, and Readdle's troubleshooting page about enabling IMAP for Gmail suggests IMAP, unverified. What is documented is that Readdle's servers hold an OAuth token for Gmail, Outlook and Yahoo and the actual password for Exchange, AOL and custom IMAP, because "Spark requires the server-side processing to send you push notifications". Send Later mail, including attachments, sits on their servers until sent, and "recent emails" are held for four hours. Spark +AI runs on Azure OpenAI and email content is shared with it. This credential relay has been criticised continuously since 2016. The desktop app has been an Electron rewrite since Spark 3 in October 2022; the launch dropped the reading pane, the persistent sidebar and separate windows, added a subscription and a paid-to-remove "Sent with Spark" signature, and drew a public response from Readdle's co-founder. Most of the gaps were patched back over 2023. Pricing today: Free, Plus $10 per user a month ($8.25 annual), Pro $20 ($16.58 annual). Gatekeeper, Priority, mute, templates, read statuses and AI are all paid.
Gatekeeper is the reason Spark is on this list. The first time a sender emails you, you decide: a horizontally scrolling row of cards labelled "New senders" sits above the inbox, each with avatar, address, latest subject and thumbs-up Accept or thumbs-down Block, plus a full-screen grid with bulk actions. Three modes per account: screen before the inbox (default), decide inside the message when you open it, or off. Blocking hides rather than deletes: blocked mail sits in a "Blocked" section of the sidebar and you unblock by opening a message there. It blocks addresses and domains, the sender is never notified, it is per account, it is not retroactive, and it is Spark-side rather than pushed into Gmail as a filter, so it only holds inside Spark. If your subscription lapses, existing blocks remain and you can only accept.
The Smart Inbox has a fixed order: People first, then Notifications, then Newsletters, each a card that can be unified, grouped per address, or per account. Spark 3 added two views on top, Focused List (chronological with priority items floated) and Unread Cards (unread mail grouped into People, Notifications, Newsletters), beside the plain Simple List; Notifications and Newsletters collapse into a single row of sender chips. Priority is manual (`I`, or per contact) and paints the row light orange with a lightning glyph. Set Aside (`G`) moves a thread to a bubble in the bottom-left corner that acts as a visible shelf; Pin (`D`) keeps it in a Pins section; Snooze (`S`) hides it until a time; Done (`E`) archives, with a toggle to show done items inline. Mute is paid and, per the help page, not currently available on Mac or Windows. Send Later is server-side, undo send waits five seconds by default, follow-up reminders go up to two months out, and Read Statuses (Pro) are ordinary tracking pixels. On the receiving side Spark blocks 1x1 pixels by default and only 1x1. Keyboard presets include Spark, Spark Classic, Apple Mail, Gmail, Superhuman and custom; the default set has no `j`/`k`. Cmd-K opens a Command Center that lists actions with their shortcuts. A "Home Screen" wallpaper greeting appears after idle time with counts of people, newsletters and notifications.
Reviewers describe the current app as heavier and busier than the 2019 version, with settings sprawling across dozens of screens and AI upsell on every surface; the free plan is thin after a seven-day trial; threading has caused missed mail; and the credential relay is the objection that never goes away.
![Spark Gatekeeper](screenshots/landscape/spark-gatekeeper.png)
*Gatekeeper: the "New senders" card row above the inbox with Accept and Block; below it Notifications and Newsletters collapsed into sender chip rows.*
![Spark Focused List](screenshots/landscape/spark-smartinbox.png)
*The Focused List: avatar rows, pinned items, Today and Yesterday sections, bundles as chips.*
![Spark inbox view picker](screenshots/landscape/spark-inboxviews.png)
*The view picker (Focused List, Unread Cards, Simple List) over an Unread Cards inbox grouped per account; orange rows are Priority.*
Worth stealing:
- Gatekeeper's interaction: a first-contact strip above the inbox, sticky per sender and per domain, silent to the sender, with a visible Blocked bucket instead of deletion. Implement it as Gmail filters and labels so it holds outside our client.
- Done as the archive verb and Set Aside as a visible shelf distinct from snooze.
- Cmd-K palette that teaches the shortcuts; shipping a Gmail preset and a Superhuman preset.
Not for us:
- The credential relay for push and Send Later.
- Fixed People, Notifications, Newsletters ordering with bundles collapsed into chip rows; it hides subjects and the classification is opaque.
- Electron, the Home Screen wallpaper, AI upsell everywhere, a "Sent with Spark" signature.
Sources: https://sparkmailapp.com, https://sparkmailapp.com/pricing, https://sparkmailapp.com/features, https://sparkmailapp.com/privacy, https://support.readdle.com/spark/privacy/privacy-explained, https://support.readdle.com/spark/spark-onboarding/accept-or-block-new-senders, https://sparkmailapp.com/help/set-up-focus/accept-or-block-new-senders, https://sparkmailapp.com/help/manage-your-inbox/customize-your-inbox, https://support.readdle.com/spark/personalization/customize-your-smart-inbox, https://sparkmailapp.com/help/set-up-focus/set-aside-vs-pin-vs-snooze, https://sparkmailapp.com/help/set-up-focus/mark-as-done, https://sparkmailapp.com/help/sending-emails/pin-and-priority, https://sparkmailapp.com/help/spark-for-teams/read-statuses, https://sparkmailapp.com/help/tips-tricks/use-keyboard-shortcuts, https://sparkmailapp.com/help/set-up-focus/spark-command-center, https://sparkmailapp.com/help/tips-tricks/how-to-enable-split-view-in-spark, https://sparkmailapp.com/features/spark-ai, https://support.readdle.com/spark/troubleshooting/enable-the-imap-protocol-for-gmail-and-g-suite-accounts, https://mjtsai.com/blog/2016/12/01/spark-mail-stores-credentials-in-cloud/, https://mjtsai.com/blog/2022/10/04/spark-switches-to-electron-and-subscriptions/, https://forums.macrumors.com/threads/popular-email-client-spark-gets-major-redesign-for-mac-moves-to-subscription-model.2363830/, https://appleinsider.com/articles/23/01/13/spark-mail-211-review-e-mail-organizer-with-gatekeeper-smart-inbox, https://cmdk.email/post/spark-mail-review/, https://thebusinessdive.com/spark-review
## Apple Mail categories
Apple's categorisation shipped in iOS 18.2 (December 2024) and reached the Mac in macOS 15.4 (31 March 2025). macOS 26 and iOS 26 added nothing to it beyond Liquid Glass toolbars; Apple's own "What's new in Mail" page for Tahoe still lists only Categories. The model is four buckets. Primary: "personal messages and time-sensitive information". Transactions: "confirmations, receipts, and shipping notices". Updates: "news, newsletters, and social updates". Promotions: "coupon and sales emails". The one rule that makes it survivable: a message in Transactions, Updates or Promotions that contains time-sensitive information is also shown in Primary. Classification is on-device machine learning, does not need Apple Intelligence hardware, and runs on every account in Mail including Gmail over IMAP, ignoring Gmail's own `CATEGORY_*` labels and classifying locally. Priority messages (time-sensitive mail floated to the top of Primary) and the one-line summaries under unread rows do need Apple Intelligence.
On iOS the categories are four icon pills under the Inbox title; the selected one expands with its label and colour (blue, green, purple, red), and swiping the row reaches All Mail. On macOS the same pill row sits at the top of the message list column in a standard three-column window, with "Show Mail Categories" in the More menu and the View menu; clicking the selected category again returns to All Mail. Inside Transactions, Updates and Promotions, messages from the same sender are grouped into a digest row showing the sender's logo and bulleted recent subjects; tapping opens a digest view with bulk actions. Primary is never grouped. Group by Sender can be switched off, and iOS 18.5 moved the Categories, Group by Sender and Show Contact Photos toggles into Settings. The override is "Categorize Sender" (control-click on Mac, swipe then More on iPhone): all current and future messages from that sender move to the chosen category, and the override syncs across devices.
The rest of Mail on macOS is relevant mainly as a floor. There is no snooze; Remind Me (1 hour, tonight, tomorrow, later) resurfaces a message at the top of the inbox. Send Later exists but needs the Mac awake with Mail open. Undo Send defaults to 10 seconds. Shortcuts are modifier chords only (Ctrl-Cmd-A archive, Shift-Cmd-D send, Ctrl-Cmd-M move to predicted mailbox), no single-key mode. Mail Privacy Protection hides your IP and prefetches remote content in the background so senders cannot see when or whether you opened a message.
Criticism landed fast. Six Colors titled its review "iOS 18.2 Mail is a misfire": Informed Delivery in Transactions, same-day delivery alerts not urgent enough for Primary, its own newsletter in Promotions, unread badge counting only Primary, a digest header that wastes space on a giant avatar while "the subject is crammed together and truncated", and no way to collapse an expanded digest. Macworld's line was that dozens of messages "could go unread for hours or even days". MacRumors readers: "now I've got six inboxes to check instead of one". AppleInsider, after months on the Mac, found "not a single reason to disagree with the automatic categorization". Both are true; it depends on your mail.
![Apple Mail categories on macOS](screenshots/landscape/applemail-categories-mac.png)
*macOS Tahoe Mail: the Primary pill row above the message list in a three-column window, Summarize button in the message header.*
![Apple Mail categories on iOS](screenshots/landscape/applemail-categories-ios.png)
*iOS Mail with Promotions selected: senders grouped into digest rows with bulleted recent subjects.*
![Apple Mail Transactions on macOS](screenshots/landscape/applemail-transactions-mac.png)
*macOS 15.4 with Transactions selected and the first-run "bundled by sender" explainer banner.*
Worth stealing:
- The four-bucket taxonomy seeded from Gmail's `CATEGORY_*` labels, with no ML needed, and the rule that time-sensitive mail from any bucket also appears in Primary.
- Categorize Sender as a sticky per-sender override with an immediate visible move; All Mail one keystroke away.
- Classification on the client; remote content handled privately by default.
Not for us:
- The sender digest as built: truncated subjects, oversized logos, no collapse, no keyboard path.
- Primary-only badge counts; categories on by default with the exit buried.
- Modifier-only shortcuts, no snooze, Send Later that needs the machine awake.
Sources: https://support.apple.com/guide/mail/mlhlp1190/mac, https://support.apple.com/guide/iphone/use-categories-iphfe4a36baf/ios, https://support.apple.com/guide/mail/whats-new-cpmlwn/mac, https://support.apple.com/guide/mac-help/use-apple-intelligence-in-mail-mchlb2dbea8f/mac, https://support.apple.com/guide/mail/keyboard-shortcuts-mlhlb94f262b/mac, https://support.apple.com/guide/mail/protect-email-privacy-mlhlp1205/mac, https://support.apple.com/en-us/122868, https://appleinsider.com/articles/25/03/31/macos-sequoia-154-arrives-with-apple-mail-categories-password-timers-and-more, https://appleinsider.com/articles/24/06/25/apples-on-device-email-categorization-is-a-feature-years-in-the-making, https://sixcolors.com/post/2024/12/ios-18-2-mail-is-a-misfire/, https://www.macworld.com/article/2585948/how-to-ios-18-macos-15-change-mail-categories-list-view.html, https://forums.macrumors.com/threads/ios-18-2-heres-how-mail-categories-work.2445355/, https://9to5mac.com/2026/03/23/apple-mail-has-a-hidden-feature-that-solved-my-biggest-inbox-problem/, https://www.techradar.com/phones/ios/apples-first-ios-18-5-beta-makes-it-easier-to-get-the-old-style-apple-mail-back
## Fastmail and Proton Mail
These are hosted mail services with their own clients, not clients over someone else's mailbox, so they matter here for two things: what a no-ads, privacy-forward inbox looks like, and a couple of specific mechanisms.
Fastmail is an Australian company (since 1999, servers in New York) whose native protocol is JMAP, which their web, mobile and desktop apps all speak; IMAP, SMTP, CalDAV and CardDAV are also available and a public JMAP API takes bearer tokens. Individual is $6 a month or about $5 annual, Duo $10, Family $14, business tiers $4 to $10 per user, 30-day trial. It finally shipped a desktop app for Mac, Windows and Linux in October 2025, and it is a wrapper around the web app (reported as Electron by third parties; Fastmail's own post does not say). The useful idea is the per-account "labels or folders" switch: pick Labels and existing folders convert, a message can carry many labels, Archive becomes All Mail as the only place an unlabelled message lives, and the Archive button just removes the Inbox label. That is Gmail's model, and Fastmail chose it deliberately. Shortcuts are Gmail-style (`j`/`k`, `o`, `u`, `c`, `r`, `a`, `f`, `d`, `y` archive, `!` spam, `l`, `m`, `/`, `g` then a folder name) and not remappable. Snooze, scheduled send (with a Scheduled folder), undo send, pin and mute conversation all exist; Pinned, Snoozed and Scheduled are visible mailboxes. Read receipts can be requested but incoming requests are never answered. There is no pixel detection: every remote image is fetched through Fastmail's servers so the host never sees your IP, and you can block remote images by default for all senders or only unknown ones. No end-to-end encryption by design, and the Five Eyes jurisdiction argument against it is real. Masked Email, built as a JMAP extension and wired into 1Password and Bitwarden, is the other idea worth noting.
Proton Mail is Swiss, OpenPGP end-to-end between Proton users and zero-access encrypted at rest for everything else, with no IMAP except through the paid Bridge daemon. Web, iOS, Android, and Electron desktop apps for Windows, macOS and Linux. Free (1 GB), Mail Plus $4.99 a month or $3.99 annual, Unlimited $12.99 or $9.99 annual, Duo $14.99, Family $29.99. The mechanism to copy is "enhanced tracking protection": on by default, it strips known tracking pixels, loads remaining remote images through Proton's proxy, and on web rewrites links to remove known UTM-style parameters, then shows a shield badge in the message header with a count and a tooltip such as "Trackers blocked: 3, Links cleaned: 6". A separate "Confirm link URLs" modal, also on by default, intercepts external links. Snooze, scheduled send, undo send and requestable read receipts exist. Shortcuts are on by default but non-standard (arrows to move, `a` archive, `t` trash, `.` star, `g` then `i`/`d`/`s`/`a`/`x`/`t` to jump, Shift-Space for a command palette, no `j`/`k`), and there is a long-standing user request for a Gmail-compatible set plus an unofficial browser extension that exists purely to remap them. Layout is Column (list beside reading pane, default) or Row (message replaces list, no preview). Proton Scribe, the writing assistant, can run fully on-device after a 4 GB model download on web and desktop, which is the right way to offer AI. Criticism: Bridge makes every third-party client second-class, encrypted bodies mean search needs a local index the web app builds slowly, and the desktop app is a web wrap with settings that do not sync between devices.
![Fastmail web inbox](screenshots/landscape/fastmail-inbox-light.png)
*Fastmail web app: labels sidebar, label chips on rows, a pinned thread, conversation view with collapsed messages.*
![Fastmail desktop app](screenshots/landscape/fastmail-desktop-app.png)
*The October 2025 Fastmail desktop app for Mac: the same web UI in a native window with a Snoozed folder visible.*
![Proton Mail desktop app](screenshots/landscape/proton-desktop-app.png)
*Proton Mail desktop app on Linux: three-pane with folders and labels in the sidebar.*
![Proton tracker badge](screenshots/landscape/proton-tracker-badge.png)
*Proton's tracker shield in the message header: "Trackers blocked: 3, Links cleaned: 6", plus the mailing-list Unsubscribe banner.*
Worth stealing:
- Fastmail's labels model spelled out plainly: Archive removes Inbox, All Mail is ground truth, many labels per message.
- Fastmail's `j`/`k`/`y`/`g`-jump key set and Pinned, Snoozed, Scheduled as visible mailboxes.
- JMAP's state-token-plus-changes mental model for our local sync layer even though we talk to Gmail.
- Proton's tracker count badge and link cleaning, shown per message; the mailing-list banner with one-click unsubscribe from `List-Unsubscribe` headers.
- On-device AI as an explicit optional download, never a cloud default.
Not for us:
- Image proxying as the only tracker defence (a native client can strip pixels locally and say what it stripped).
- Non-standard shortcut sets; Row layout with no preview.
- Electron desktop apps from companies that own the protocol; being genuinely native is the differentiator.
Sources: https://www.fastmail.com/pricing/, https://www.fastmail.com/features/, https://www.fastmail.com/blog/desktop-app/, https://www.fastmail.com/for-developers/integrating-with-fastmail/, https://www.fastmail.help/hc/en-us/articles/360058753554-Setting-up-and-using-labels, https://www.fastmail.help/hc/en-us/articles/360058753534-Keyboard-shortcuts, https://www.fastmail.help/hc/en-us/articles/1500000278102-Blocking-remote-images, https://www.fastmail.help/hc/en-us/articles/1500000278162-Read-receipts, https://www.fastmail.com/blog/how-and-why-we-built-masked-email-with-jmap-an-open-api-standard/, https://coywolf.com/news/productivity/fastmail-launches-native-desktop-apps-for-mac-windows-and-linux/, https://cyberinsider.com/email/reviews/fastmail/, https://proton.me/mail, https://proton.me/mail/pricing, https://proton.me/support/proton-plans, https://proton.me/support/email-tracker-protection, https://proton.me/blog/tracking-links-protection, https://proton.me/support/link-confirmation, https://proton.me/support/keyboard-shortcuts, https://proton.me/support/change-inbox-layout, https://proton.me/support/read-receipts, https://proton.me/blog/proton-scribe-writing-assistant, https://proton.me/blog/proton-mail-desktop-app, https://github.com/ProtonMail/inbox-desktop, https://protonmail.uservoice.com/forums/284483-proton-mail/suggestions/38545198-improve-keyboard-shortcuts-gmail-like
## Open-source Gmail API clients
Zero (Mail-0/Zero, 0.email) is the visible one: "An Open-Source Gmail Alternative for the Future of Email", MIT licensed, 10.8k stars, a YC Spring 2025 company of three people, public beta May 2025. The company has since pivoted: 0.email now says "0.email is now Orchid", an executive-assistant product over iMessage and SMS, and the Zero repo's default branch last moved on 31 August 2025 with three commits to `main` in May 2026 titled "fix build", "idk atp" and "ugh". Issues are auto-closed by a stale bot after three days. It is a hosted web app you self-host, not a desktop or mobile app. The README still says Next.js and Node; `wrangler.jsonc` says otherwise. The real stack is a React front end deployed as a Cloudflare Worker, a Hono plus tRPC server on Workers, six Durable Object classes (ZeroAgent, ZeroMCP, ZeroDB, ZeroDriver, ThreadSyncWorker, ShardRegistry), Cloudflare Workflows for initial sync, three Queues, R2 for thread bodies, Vectorize for embeddings, Workers AI, Drizzle with Postgres, Better Auth, and the googleapis SDK, plus a Microsoft Graph driver. Self-hosting therefore needs a Google OAuth client, a GCP service account with Pub/Sub admin rights, the whole Cloudflare stack, a billing SDK key, Twilio credentials and model provider keys.
Its Gmail usage is worth reading because it is the textbook pattern. Scopes are `https://mail.google.com/` plus `gmail.modify`. Initial sync pages `threads.list` at 100 per page and calls `threads.get` with `format=full` for each, writing into a per-connection Durable Object with SQLite, bodies to R2, embeddings to Vectorize. Incremental sync creates a Pub/Sub topic per connection, grants `[email protected]` publish rights, subscribes to a push URL, and calls `users.watch` on INBOX; the handler reads the historyId, queues it, and a consumer calls `history.list` and re-syncs the affected threads; a cron renews expiring watches. Rate limiting retries 429 and 403 `userRateLimitExceeded` and `quotaExceeded` up to ten times with a fixed 60-second delay. Features: a unified inbox with category tabs (Primary, Important, Personal, Updates, Promotions), "ask anything about your emails" chat, AI summaries and labels, AI compose, snooze, scheduled send, pinned threads, Cmd-K, an MCP server. The one Show HN comment was from a Superhuman user: "until Zero has real keyboard navigation, i'll have to wait". Issue 1839, "[oauth]: This app is blocked" (Google refusing sign-in to the unverified client), was closed by the stale bot unanswered, which is the self-hosting problem in one line: with a full-mailbox scope every self-hoster is either stuck in Testing mode (100 users, tokens expire after 7 days) or doing restricted-scope verification themselves.
lieer (gmi) is the feasibility reference nobody talks about: a Python tool, GPL, maintained since 2017 and still pushed to in April 2026, that pulls Gmail through the API into a maildir and mirrors Gmail labels as notmuch tags, two-way. It stores the last historyId and uses `history.list` for partial pulls, falls back to a full resync, pushes local tag changes back with `gmi push`, and sends over the API with thread matching on In-Reply-To. Its invariants are the ones any Gmail-API client ends up with: a message may carry only one of inbox, spam or trash; drafts and sent are read-only from Gmail's side; archive means removing the inbox tag; muted cannot sync because the API has no mute. It runs at million-message scale, ships a shared public OAuth client or takes your own, and its README is candid: "You don't need to verify your application, you'll just get a scary warning about unverified applications."
The closest technical comparators are two Tauri apps. Velo (Apache-2.0, 701 stars, created February 2026, quiet since June 2026) is Tauri v2 with a Rust backend and a React front end, Gmail via the REST API with historyId sync, OAuth PKCE where the user supplies their own client ID, SQLite with FTS5, a command palette, split-inbox tabs, offline, remote-image blocking, sandboxed rendering, and optional cloud AI; the caveat is that the Gmail sync logic lives in TypeScript inside the webview, not in Rust. Pebble (AGPL, 583 stars, created April 2026, active in August 2026) is Tauri 2 plus Rust with rusqlite and Tantivy full-text search, but talks to Gmail over IMAP, and the UI is Chinese-first. Beyond those, every established open-source client goes through IMAP: Thunderbird and Betterbird (IMAP plus OAuth, labels become duplicated folders, All Mail duplicates everything), Geary (Vala, last release May 2024), Evolution, KMail, Mailspring (Electron UI over a C++ mailsync engine, stalled 2022 to 2024 with draft-deletion and locked-database complaints, then twelve releases between January and July 2026), and the Rust and Go terminal clients Himalaya, meli and aerc. Nylas Mail, the last well-funded open-source attempt, depended on Nylas's cloud sync and was sunset in 2017.
What they collectively got wrong is easy to state. Nobody shipped a good native desktop client on the Gmail API: Zero is a Cloudflare web app whose company pivoted, Velo put its sync in a webview, and everyone else inherited IMAP's labels-as-folders mess. What they show is feasible: history-based delta sync, labels as tags, two-way label push, API send, and watch plus Pub/Sub for push have all been done in the open for years, and Velo shows PKCE with bring-your-own client ID works for a desktop app without a secret. The constraints to plan for, from Google's current pages: every mailbox-reading scope is restricted (`gmail.modify`, `gmail.readonly`, `mail.google.com`), so brand verification, domain verification, a privacy page, a scope justification and a demo video are unavoidable and must be repeated every 12 months. The security assessment (CASA, now Assurance Levels AL1 and AL2, roughly $500 to $1,800 and about $4,500 respectively, 4 to 12 weeks end to end, annual) is triggered for apps that "have the ability to access data from or through a third-party server"; read plainly, a client that never routes mail through our servers needs scope verification but not CASA, unverified, and worth confirming with Google before betting the roadmap on it. Live quota: 1,200,000 units a minute per project, 6,000 units a minute per user, `messages.get` 20 units, `threads.get` 40, `messages.list` 5, `history.list` 2, `messages.send` 100, `users.watch` 100, and a watch must be renewed at least every 7 days. Threading is Gmail's: to land in an existing thread you set threadId and the RFC 2822 References and In-Reply-To headers and a matching subject; labels are per message, so "thread has label" is a client-side aggregate.
![Zero inbox and thread](screenshots/landscape/zero-inbox-thread.png)
*Zero: sidebar, Primary list with a Pinned group, thread view with an AI Summary box.*
![Zero category tabs](screenshots/landscape/zero-inbox-categories.png)
*Zero on a real Gmail account: Primary, Important, Personal, Updates and Promotions tabs over the list.*
![Velo inbox](screenshots/landscape/velo-inbox.png)
*Velo (Tauri plus Rust, Gmail REST API): dark inbox with AI Summary, Quick Replies and a contact side panel.*
![Pebble inbox](screenshots/landscape/pebble-inbox.png)
*Pebble (Tauri plus Rust, IMAP): three-pane inbox with hover actions.*
![Thunderbird inbox](screenshots/landscape/thunderbird-inbox.png)
*Thunderbird: the IMAP baseline, unified folders, tags and a threaded list.*
Worth stealing:
- lieer's invariants verbatim: one of inbox, spam or trash; drafts and sent read-only; archive is drop INBOX; muted not syncable.
- The watch, Pub/Sub, historyId, `history.list` pipeline, and cron renewal of watches; Zero's code is a readable reference.
- Velo's PKCE-only OAuth with bring-your-own client ID as the fallback for people Google will not let in, and a local FTS index.
- Tantivy or SQLite FTS5 for local search, as Pebble and Velo do.
Not for us:
- Zero's architecture: server-side mailbox copies, six Durable Object classes and a service account with Pub/Sub admin rights to run an inbox. Local SQLite and `history.list` at 2 units a call need none of it and avoid the "third-party server" CASA trigger.
- Sync logic in a webview.
- The AI-chat-first inbox; the one keyboard user who showed up to Zero's launch walked away.
Sources: https://github.com/Mail-0/Zero, https://github.com/Mail-0/Zero/blob/staging/apps/server/wrangler.jsonc, https://github.com/Mail-0/Zero/blob/staging/apps/server/src/lib/driver/google.ts, https://github.com/Mail-0/Zero/blob/staging/apps/server/src/lib/factories/google-subscription.factory.ts, https://github.com/Mail-0/Zero/blob/staging/apps/server/src/lib/gmail-rate-limit.ts, https://github.com/Mail-0/Zero/issues/1839, https://0.email, https://www.ycombinator.com/launches/NTI-zero-ai-native-email, https://www.ycombinator.com/companies/orchid-ai, https://news.ycombinator.com/item?id=43862892, https://github.com/gauteh/lieer, https://github.com/gauteh/lieer/blob/master/docs/index.md, https://github.com/avihaymenahem/velo, https://github.com/QingJ01/Pebble, https://github.com/Foundry376/Mailspring, https://github.com/Foundry376/Mailspring-Sync, https://support.mozilla.org/en-US/kb/thunderbird-and-gmail, https://www.nylas.com/blog/sunsetting-nylas-mail-development/, https://developers.google.com/gmail/api/auth/scopes, https://developers.google.com/identity/protocols/oauth2/production-readiness/restricted-scope-verification, https://support.google.com/cloud/answer/15549945, https://developers.google.com/workspace/gmail/api/reference/quota, https://developers.google.com/workspace/gmail/api/guides/push, https://developers.google.com/gmail/api/guides/threads, https://deepstrike.io/blog/google-casa-security-assessment-2025, https://www.unipile.com/integrating-google-oauth-2-0-user-authentication-into-your-app/
## Big Mail, Canary, Missive, Front
Big Mail (bigmail.app, by Phillip Caudell; bigmail.com is an unrelated placeholder) was a native Mac and iOS IMAP and Gmail client launched February 2021 that sorted mail into "Scenes" (Conversations, Newsletters, Purchases, Events, Notifications), blocked trackers by default and had a HEY-style "Bouncer" for screening new senders, at $10 a month or $6.49 annual. A "Big Mail 2" rewrite never left TestFlight; the developer's last post is February 2024 and the forum's most recent thread is titled "Big Mail 2 TestFlight is dead" (March 2025). It is the closest anyone came to our product on native Apple frameworks, and it died as a one-person project; treat it as a warning about scope, not a competitor.
Canary Mail (canarymail.io) is a native macOS, iOS, Windows and Android client for Gmail (Google OAuth), Exchange, iCloud, Yahoo and IMAP, sold on PGP and password-protected "SecureSend" plus an AI copilot, with pixel-based read receipts and tracker blocking on by default. Annual or lifetime only: Free, Growth $36 a year (about $3 a month), Pro+ $100 a year (about $10). Three-pane, customisable shortcuts, snooze and undo on every tier, send later on Growth and up.
Missive (missiveapp.com) is a shared team inbox with chat over Gmail, Outlook and IMAP on web, macOS, Windows, iOS and Android (no Linux). Per user per month: Starter $18 or $14 annual, Productive $30 or $24, Business $45 or $36, no free plan. Gmail keyboard preset (`j`/`k`, `e`, `c`), Cmd-K command bar, snooze, send later, undo send, rules. It removed read receipts in September 2020 on privacy grounds and auto-blocks read trackers, which is the right call and rare in the team-inbox category.
Front (front.com) is the enterprise shared inbox and ticketing tool: web, Mac, Windows, iOS, Android; Gmail via Google OAuth two-way sync (API or IMAP not stated), Office 365, IMAP. Per seat per month: Starter $35 or $25 annual (up to 10 seats), Professional $85 or $65 (up to 50), Enterprise $105 annual. Snooze, send later, undo send, pixel-based "Seen receipts" per channel, Front and Gmail keyboard schemes with Cmd-K.
Sources: https://bigmail.app/, https://discuss.bigmail.app/t/big-mail-2-testflight-is-dead/211.json, https://discuss.bigmail.app/t/app-development-update/188.json, https://sixcolors.com/post/2021/06/big-mail-may-not-be-my-next-email-client-but-its-aiming-at-the-future/, https://9to5mac.com/2021/01/22/big-mail-email-radical-new-ui/, https://canarymail.io/pricing, https://canarymail.io/features/security, https://canarymail.io/help/read-receipts-mac-iphone, https://missiveapp.com/pricing, https://missiveapp.com/download, https://missiveapp.com/docs/advanced-features/shortcuts, https://missiveapp.com/blog/life-and-death-of-read-tracking, https://front.com/pricing, https://help.front.com/en/articles/2189, https://help.front.com/en/articles/2034, https://help.front.com/en/articles/2072
## Comparison table
Prices are USD per month; "annual" is the per-month rate when billed yearly. Reading pane: "optional" means the user can switch between a split pane and a full-page thread. Sender screening means HEY-style accept or block of first-time senders, not spam filtering. Keyboard-first: strong means full keyboard navigation with `j`/`k` and a command palette, ok means a decent shortcut set, weak means mouse-first. HEY and Superhuman appear here only for comparison; they have their own documents.
| App | Backend | Platforms | Price per month | Reading pane | Sender screening | Split or bundled inbox | Snooze | Send later | Undo send | Read receipts | Keyboard-first | Tracker blocking | Open source |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| HEY | Own service | Web, Mac, Windows, Linux, iOS, Android | $99 a year personal (about $8.25, no monthly); Domains $12 per user | No (page per thread) | Yes (The Screener) | Imbox, Feed, Paper Trail | Yes (Bubble Up) | Yes | Yes | No | Strong | Yes | No |
| Superhuman | Gmail API, Outlook | Mac, Windows, web, iOS, Android | Starter $30 ($25 annual); Business $40 ($33 annual); no free tier | Optional (split by default) | No | Split Inbox by filters and AI | Yes | Yes | Yes | Yes (Read Statuses) | Strong | Partial (opt-in pixel blocking) | No |
| Mimestream | Gmail API only | macOS (iOS in TestFlight) | $4.99 ($4.17 annual); 14-day trial; no free tier | Yes (three-pane) | No | Gmail category inboxes | Partial (client-only, Labs) | No | Yes | No | Ok (Gmail `j`/`k` set, no palette) | Yes (default on) | No |
| Shortwave | Gmail API only | Web, Mac, Windows, iOS, Android | Business $30 ($24 annual); Premier $45 ($36); Max $120 ($100); no free tier | Optional (side panel, fullscreen) | No | Splits as tabs plus label and sender bundles | Yes | Yes | Yes (server-side, 10 s) | Yes (paid, all tiers) | Strong | Yes (proxy plus pixels) | No |
| Notion Mail | Gmail API only | Web, Mac, iOS; shuts down 22 Sep 2026 | Free; AI needs Notion Business $20 per seat annual | Optional (side peek, centre peek, full page) | No | Views (saved filter and group) plus AI auto-label | Yes ("Set reminder") | Yes | Unverified | No | Strong | Partial (image proxy, no pixel blocking) | No |
| Spark | IMAP, Exchange, Gmail via OAuth (transport unverified); tokens held on Readdle servers | Mac, Windows, iOS, Android | Free; Plus $10 ($8.25 annual); Pro $20 ($16.58 annual) | Optional (Split View setting) | Yes (Gatekeeper, paid) | Smart Inbox: People, Notifications, Newsletters | Yes | Yes (server-side) | Yes (5 s) | Paid (Pro, pixel-based) | Ok (Cmd-K, presets, no `j`/`k` by default) | Partial (1x1 pixels only) | No |
| Apple Mail | IMAP, Exchange, iCloud | macOS, iOS, iPadOS | Free | Yes | No | Categories: Primary, Transactions, Updates, Promotions | No (Remind Me) | Yes (Mac must be awake) | Yes (10 s) | No | Weak (modifier chords only) | Yes (Mail Privacy Protection, opt-in) | No |
| Fastmail | Own service (JMAP, IMAP) | Web, iOS, Android, desktop wrapper for Mac, Windows, Linux | $6 ($5 annual); Duo $10; Family $14; no free tier | Optional | No | None (labels or folders, rules, pins) | Yes | Yes | Yes (15 s) | Request only | Strong (`j`/`k`, `y`, `g` jump; not remappable) | Yes (images proxied, no count) | No (open protocol) |
| Proton Mail | Own service (E2E; Bridge for IMAP, paid) | Web, Windows, Mac, Linux, iOS, Android | Free; Mail Plus $4.99 ($3.99 annual); Unlimited $12.99 ($9.99 annual) | Optional (column or row) | No | Categories rolling out in 2026 (unverified detail) | Yes | Yes | Yes (0 to 20 s) | Request only | Ok (non-standard keys, Shift-Space palette) | Yes (default on, count badge, link cleaning) | Yes (clients) |
| Zero / Mail-0 | Gmail API, Microsoft Graph; hosted or self-hosted web app | Web | Free (one account); Pro $20 (about $10 annual), unverified | Yes | No | Category tabs plus AI labels | Yes | Yes | Unverified | Unverified | Weak (Cmd-K exists, no real list navigation) | Unverified | Yes (MIT) |
| Big Mail | IMAP, Gmail | Mac, iOS, iPadOS; abandoned | $10 ($6.49 annual) in 2021 | Yes | Yes (The Bouncer) | Scenes by type | Unverified | Unverified | Yes | No | Weak | Yes (default on) | No |
| Canary | Gmail via OAuth, Exchange, iCloud, Yahoo, IMAP | Mac, Windows, iOS, Android | Free; Growth about $3 ($36 a year); Pro+ about $10 ($100 a year); no monthly billing | Yes | No | AI prioritisation | Yes | Yes (Growth and up) | Yes (5 s) | Yes (pixel-based) | Ok (customisable) | Yes (default on) | No |
| Missive | Gmail, Outlook, IMAP (Gmail transport unverified) | Web, Mac, Windows, iOS, Android | Starter $18 ($14 annual); Productive $30 ($24); Business $45 ($36) per user | Yes | No | Team inboxes, rules, filtered views | Yes | Yes | Yes | No (removed 2020) | Strong (Gmail preset, Cmd-K, remappable) | Yes | No |
| Front | Gmail via OAuth (transport not stated), Office 365, IMAP | Web, Mac, Windows, iOS, Android | Starter $35 ($25 annual); Professional $85 ($65); Enterprise $105 annual, per seat | Yes | No | Shared inboxes, tags, rules, views | Yes | Yes | Yes | Yes (Seen receipts) | Ok (Gmail scheme, Cmd-K) | Unverified | No |
## Patterns across the field
Everyone has converged on the same verbs. Archive is called Done and bound to `E` in Shortwave, Spark and Missive; snooze, send later and a 5 to 15 second undo are table stakes; and every client that takes keyboards seriously ships Gmail's `j`/`k`/`e`/`c`/`/`/`g`-then-letter set, as the default (Shortwave, Notion Mail, Fastmail) or as a preset (Mimestream, Spark, Missive, Front). Cmd-K is expected: seven of the fourteen have one, and the two serious clients without it (Mimestream, Fastmail) get asked for it in every review. Image proxying or pixel blocking is universal; the only variable is whether the client says what it blocked (Proton's count badge) or stays silent (Fastmail, Notion, Apple).
Layout has settled on a toggle, not a position. HEY is the only page-per-thread holdout; Superhuman, Shortwave, Notion Mail, Spark and Proton all make side pane versus full page a setting, and the rest are three-pane. Ship the toggle and pick a calm default.
The real fork is how mail gets sorted before you see it. One camp classifies automatically: Gmail's tabs, Apple's on-device buckets, Spark's People, Notifications, Newsletters, Notion's AI auto-label, Proton's new categories, Zero's AI labels. The other asks the human once per sender: HEY's Screener, Spark's Gatekeeper, Big Mail's Bouncer. Automatic sorting is what people complain about (Six Colors on Apple, the "six inboxes" thread, Spark's opaque chips); screening is what people who have it refuse to give up, and only Spark offers both, paid and through a credential relay. Beside that, labels (Mimestream), saved views (Notion, Front, Missive) and split tabs (Superhuman, Shortwave, Zero) are three names for a saved query over labels with a group-by. Notion was the only one to say so; Shortwave's collapsed bundle row is the only one that made the query a single object you can act on with one key.
The gaps are specific. No native desktop client on the Gmail API does sender screening; Mimestream is the only native Gmail-API client and it has no screening, no palette, no bundles and Apple Mail's row density. Nobody has solved snooze and scheduled send on the Gmail API without a server holding tokens: Mimestream refuses and ships neither honestly, the others run servers with mailbox access, and the obvious middle (snooze as remove-INBOX plus a label, woken by whichever device is on; a `gmail.send`-only service for scheduled send) is on Mimestream's roadmap and built by nobody. Shortwave, Notion Mail and Zero advertise multi-account and have no unified inbox. Linux has no native Gmail client at all. Gmail's category settings cannot be read through the API, so every client that shows categories guesses. List-Unsubscribe is surfaced well only by Proton and Gmail. Every open-source Gmail-API client is a web app or keeps its sync in a webview, and the one native attempt that came close, Big Mail, was one person and stopped. The combination we want (native, Gmail API, screening plus categories from existing labels, dense quiet list, `j`/`k` plus Cmd-K, tokens on device, no AI in the critical path) does not exist, but every piece of it has been proven separately by someone on this list.
Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff. Show more