DEV Community

Sergey Ilin
Sergey Ilin

Posted on

Gmail has a hidden Atom feed. I built a Chrome extension on it with no OAuth and no backend. tags: chrome, javascript, webdev, showdev

Every Gmail notifier extension asks you to sign in with OAuth and hand a stranger read access to your inbox — to answer the question "is there new mail?"

You don't need any of that. Gmail has served an Atom feed of unread mail since 2004, and it still works:

https://mail.google.com/mail/u/0/feed/atom
Enter fullscreen mode Exit fullscreen mode

Open it in a browser where you're signed in. It just returns XML. It authenticates with your existing session cookie — no token, no consent screen, no API quota.

<fullcount>3</fullcount>
<entry>
  <title>Re: the thing</title>
  <summary>sounds good, ship it</summary>
  <author><name>Sam</name><email>sam@example.
  <link href="...message_id=18f2c9a1b..." />
</entry>
Enter fullscreen mode Exit fullscreen mode

<fullcount> is the exact unread count for the whole mailbox — not the ~20 entries the feed actually returns. That distinction matters: the badge can be accurat complete.

Category feeds work too, which is how you getly":

/feed/atom/%5Esq_ig_i_personal   # Primary
/feed/atom/%5Esq_ig_i_promo      # Promotions
Enter fullscreen mode Exit fullscreen mode

Three things that were harder than the feed

1. The feed only returns unread mail. So a message disappearing is the only signal that you read it. If you
render the feed directly, your panel is a staot an inbox. Fix: keep a small local archive(last 40 per category) and mark vanished messages as read instead of dropping them. They stay in the list, greyed out.
That one decision is the difference between at looks like Gmail.

**2. Every manual refresh wants to be a notifen` sets break the moment you switch categoryfilters. Two conditions, both required:

js
if (!seenSet.has(e.id) && arrivedSinceLastChe

Plus a seenInit flag so the very first poll after install never fires anything.

3. MV3 service workers can't play audio. No Audio, no AudioContext. You need an offscreen document just to play a chime:

js
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['AUDIO_PLAYBACK'],
justification: 'notification sound',
});

The parser is a regex, deliberately:

js
// ponytail: regex parser — the feed is machine-generated flat XML; switch to an
// offscreen-document DOMParser if Google eve

MV3 workers have no DOMParser either. For flat machine-generated XML, 40 lines of regex beats spinning up a second
offscreen document. The comment names the cei which is all a shortcut owes you.

The result

~200KB of vanilla JS. No framework, no build ics, no account. Three Chrome APIs in a trenchcoat: alarms, notifications, sidePanel.

The nice part isn't the size — it's that there's no backend to breach, because there's no backend. Mail never leaves the browser. That's not a privacy politure.

Shipped it as Email Checker for Gmail — free.

If you're building anything against Gmail and unread*, check the feed before you reach forthe API. It's been sitting there for 20 years.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev

The no-OAuth/no-backend angle is interesting because it changes the threat model. A local extension using an existing authenticated feed can be much simpler, but the UX needs to make the boundary obvious: what stays local, what the browser already knows, and what is never sent out.