Notes for Google OAuth reviewers
Everything a reviewer needs to assess this application, in one place. Written for the verification process rather than for prospective customers.
Last updated September 21, 2026
Status
1. What the application is
Unified E box is a centralized email-management workspace used by members of our company. The company has a large remote workforce distributed across several countries, and those members use their own personal Gmail accounts rather than Google Workspace accounts. Each member independently connects their own mailbox through Google’s consent flow.
The purpose is to give a member one interface for the mail they already handle: a single list of conversations across their connected mailboxes, search across those conversations, read and unread state that stays consistent with Gmail, and the ability to reply from the correct account. It is a mail client, not a data pipeline.
Why this is not an internal application
Users hold personal Gmail accounts, not accounts in a Google Workspace domain we control, so the application cannot be published as internal. It is external by necessity, and every user passes through the standard consent screen.
2. Scopes and justification
The application requests exactly the three scopes below. This list is generated from the same constant the application uses to build its authorization URL, so it cannot drift from what the consent screen actually shows.
https://www.googleapis.com/auth/gmail.modifyrestrictedFeatures that require it
- Fetch the messages and threads that the unified inbox displays.
- Run an incremental sync so the inbox reflects the mailbox without re-downloading it.
- Subscribe to mailbox change notifications so new mail appears without constant polling.
- Apply and remove the UNREAD label when a member marks a conversation read or unread.
- Apply and remove the STARRED label when a member stars or unstars a conversation, and show stars applied in Gmail itself.
- Save an email the member is writing as a draft in their Gmail Drafts, update it as they type, and delete it when they send or discard it.
- Download an attachment on demand when a member opens it.
Methods called
users.getProfileusers.messages.listusers.messages.getusers.messages.attachments.getusers.history.listusers.threads.modifyusers.drafts.createusers.drafts.updateusers.drafts.deleteusers.watchusers.stophttps://www.googleapis.com/auth/gmail.sendsensitiveFeatures that require it
- Send a reply or a new message that the member composes in the application.
- Thread the reply correctly onto the existing conversation in Gmail.
Methods called
users.messages.sendhttps://www.googleapis.com/auth/userinfo.emailsensitiveFeatures that require it
- Identify which mailbox just completed the consent flow, so the resulting credentials are bound to the correct account record.
- Display the connected address on the Accounts page so a member can tell their mailboxes apart.
- Prevent the same mailbox from being connected twice.
Methods called
oauth2.userinfo.gethttps://www.googleapis.com/auth/userinfo.profilenon-sensitiveFeatures that require it
- Show each connected mailbox's own profile photo in the mailbox list, so a member can tell their mailboxes apart at a glance.
Methods called
oauth2.userinfo.gethttps://www.googleapis.com/auth/calendar.eventssensitiveFeatures that require it
- Show the connected account's upcoming events on the Calendar page, beside its mail.
- Create an event, and invite guests to it, when a member schedules one from the Calendar page.
Methods called
calendar.events.listcalendar.events.insertWhy gmail.modify rather than a narrower scope
The application needs to read message content and to change read state. Read access alone (gmail.readonly) cannot mark a conversation read, and that state has to stay consistent with Gmail or the two views contradict each other. Since gmail.modify already covers the read access we need, requesting gmail.readonly alongside it would add a second restricted scope for no additional capability, so we do not. We also do not request https://mail.google.com/, which would grant permanent deletion we neither need nor want.
3. How to reproduce each scope’s use
Using a test account we can provide on request, each requested permission maps to a visible action:
- Connect a mailbox. From the Accounts page, choose Connect Gmail and complete Google’s consent screen. This exercises
userinfo.email, which binds the resulting tokens to the right account. - Watch the inbox populate. The initial sync lists and fetches messages, then registers a change-notification watch. This exercises
users.getProfile,users.messages.list,users.messages.get, andusers.watchundergmail.modify. - Open a conversation and mark it unread. The same change appears in Gmail, via
users.threads.modifyadding or removing the built-inUNREADlabel. - Star the conversation. The star appears in Gmail too, via
users.threads.modifyadding or removing the built-inSTARREDlabel. - Start an email and close it unsent. It appears in Gmail’s Drafts, created and kept current with
users.drafts.createandusers.drafts.update; discarding or sending it removes it withusers.drafts.delete. - Open an attachment. The bytes are fetched on demand through
users.messages.attachments.get; they are not stored. - Send a reply. Composing and sending exercises
users.messages.sendundergmail.send, and the reply appears correctly threaded in Gmail. - Disconnect. The token is revoked with Google and the stored credentials are deleted, after which no further Gmail API call succeeds for that mailbox.
4. Data handling summary
- Stored: message content and metadata for synced mail, attachment metadata, connected-account records, and encrypted OAuth tokens. Detailed on the Gmail integration page.
- Not stored: attachment contents, Google passwords, and any Google profile field other than the email address and account identifier.
- Encryption: OAuth tokens are encrypted at rest with AES-256-GCM using a key held outside the database; all transport is over TLS.
- Isolation: row-level security scopes every query to the caller’s organization, and each API route re-verifies the session server-side.
- Deletion: self-service disconnect revokes and deletes credentials immediately; verified deletion requests erase stored content within 30 days. See the data deletion page.
- Limited Use: no advertising, no sale of data, no AI or ML model training on message content, and no human reading of mail outside the narrow exceptions the policy permits.
5. API usage behaviour
After the initial sync, the application does not poll. It relies on Gmail change notifications and then requests only the history since its last known point, so request volume tracks real mailbox activity rather than the number of connected accounts. Watches are renewed before their seven-day expiry. Failures are retried with backoff, and a revoked grant moves the account into a re-authorization state instead of retrying indefinitely.
6. Required pages
- Homepage: /
- Privacy policy: /privacy
- Terms of service: /terms
- Gmail data use: /docs/gmail-data-use
- Data deletion: /data-deletion
- Security: /security
All of these are publicly reachable without signing in, and are served from the same domain as the OAuth redirect URI.
7. Contact during review
For anything related to this review, including a demonstration account or a walkthrough recording, contact security@uniebox.vercel.app. We aim to respond within two business days.