Why Selyf only reads your Gmail
Selyf asks for exactly one Gmail permission: gmail.readonly. Not gmail.modify, not gmail.send, not gmail.compose. This post is about what that one scope actually means in practice — what Selyf can do, what it structurally cannot do, and why the AI-drafted replies you generate never leave your browser tab on their own.
The scope, not just the pitch
When you connect your Google account, the consent screen you see is generated from a fixed list of three OAuth scopes:
openid
email
https://www.googleapis.com/auth/gmail.readonlyopenid and email are the minimum needed to know which Google account just signed in — Google treats those as non-sensitive. gmail.readonly is the one that matters: it's a real, Google-defined scope that grants read access to your mailbox and nothing else. There is no separate internal permission layer papering over a broader grant — the access token Google hands back after you consent is scoped by Google itself, not by Selyf's own code. If a future version of Selyf ever needed to send or modify mail, that would require a new consent screen, because the current token literally cannot authorize it.
This is also why Selyf can't do a handful of things you might expect from a "smart inbox" product: it can't archive a thread for you, can't mark something spam, can't apply a Gmail label, can't move mail to trash. Those all require gmail.modify, which was never requested.
What actually gets called
It's easy to say "read-only" and mean it loosely. Concretely, here is the complete list of Gmail API operations Selyf's backend calls, and what each one is for:
- Profile lookup — confirms which Gmail account is connected.
- List inbox messages — pulls message references (ids) from your INBOX label, paginated, capped per sync so one "Sync now" click has bounded cost.
- List sent thread ids — checks which threads you've already personally replied to, so a thread you've handled doesn't get flagged as "needs reply" by mistake.
- Get message metadata — subject, sender, snippet, timestamps, labels, for messages not already stored.
- Get message body — the full message content, fetched only when you actually open a specific thread, not in bulk.
- Get attachment content — an attachment's bytes, only when you explicitly download one.
- Gmail watch / stop watch — registers (or cancels) a push notification subscription via Pub/Sub, so Selyf can notice new mail without polling constantly.
- List history — the incremental "what changed since last time" feed that watch notifications trigger.
That's the entire list. There is no function anywhere in the codebase that calls Gmail's messages.send, drafts.create, messages.modify, messages.trash, or labels write endpoints — not behind a feature flag, not commented out, not planned for later in this version. The absence isn't a policy Selyf promises to follow; it's that the code to do any of those things doesn't exist, and the OAuth token wouldn't authorize it even if it did.
Why the AI draft is something you copy, not something Selyf sends
When you ask Selyf to draft a reply, the request goes to an AI provider (Anthropic or Gemini, depending on configuration) with the thread's content, and comes back as plain text. That text is shown to you in an editable box on the thread page, with a copy button next to it.
That's the entire flow. There's no "send" button next to a generated draft, because Selyf has no Gmail permission that would let a send succeed even if there were one. The draft exists only in your browser tab's state — reload the page and, unless it's been saved, it's gone. Nothing about generating a draft writes anything to your actual Gmail account. You read it, edit it if you want to, copy it, and paste it into Gmail yourself to send — the same way you'd paste text from any other tool into an email you're about to send. Selyf never sees whether you actually sent it, edited it further, or discarded it entirely, because that step happens entirely outside anything Selyf has access to.
This isn't a temporary limitation waiting on a future "auto-send" feature to ship. It follows directly from the scope decision above: a read-only token can't send mail, so the product built around it doesn't have a send button, and the draft you get is designed from the start to be copied out, not delivered automatically.