The problem with Gmail OAuth
If you've ever tried to add Gmail access to a Node.js app, you know the pain:
- Create a Google Cloud project
- Enable the Gmail API
- Configure the OAuth consent screen (publisher name, scopes, test users...)
- Create OAuth 2.0 credentials
- Handle the authorization code flow
- Exchange codes for tokens
- Store and refresh tokens
- Submit for Google's app verification (if you want more than 100 users)
That's 8 steps before you've read a single email. Most of it is boilerplate that has nothing to do with your actual product.
The shortcut: inbox.dog
inbox.dog is an OAuth proxy. It handles steps 1-6 for you. You get a client_id, redirect your user, and receive Gmail tokens. The tokens work directly with gmail.googleapis.com — inbox.dog is not in the data path.
Here's the complete flow in Node.js:
Step 1: Get your API key
curl -X POST https://inbox.dog/api/keys \
-H "Content-Type: application/json" \
-d '{"name": "my-app"}' Response:
{
"client_id": "id_abc123",
"client_secret": "sk_xyz789",
"name": "my-app",
"credits": 10
} You get 10 free credits. Each OAuth flow costs 1 credit ($0.10).
Step 2: Create a Node.js server
Install the dependencies:
npm install express inbox.dog Create server.js:
import express from "express";
import { InboxDog } from "inbox.dog";
const app = express();
const dog = new InboxDog();
const CLIENT_ID = "id_abc123"; // from step 1
const CLIENT_SECRET = "sk_xyz789"; // from step 1
// Redirect user to Gmail consent
app.get("/login", (req, res) => {
const url = dog.getAuthUrl({
clientId: CLIENT_ID,
redirectUri: "http://localhost:3000/callback",
scope: "email", // read-only access
});
res.redirect(url);
});
// Handle the callback
app.get("/callback", async (req, res) => {
const { code } = req.query;
const tokens = await dog.exchangeCode(code, CLIENT_ID, CLIENT_SECRET);
// tokens.access_token → use with Gmail API
// tokens.refresh_token → store securely, use to get new access tokens
// tokens.email → the user's email address
res.json({
email: tokens.email,
message: "Gmail connected!",
});
});
app.listen(3000, () => console.log("http://localhost:3000/login")); Step 3: Use the Gmail API
The access_token works directly with Google's Gmail API. No inbox.dog in the middle.
// List recent emails
const response = await fetch(
"https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10",
{ headers: { Authorization: `Bearer ${tokens.access_token}` } }
);
const { messages } = await response.json();
// Read a specific email
const email = await fetch(
`https://gmail.googleapis.com/gmail/v1/users/me/messages/${messages[0].id}`,
{ headers: { Authorization: `Bearer ${tokens.access_token}` } }
).then(r => r.json());
console.log(email.snippet); // Preview of the email Refreshing expired tokens
Access tokens expire after 1 hour (set by Google). Use the refresh token to get a new one — this is free, no credit cost:
const fresh = await dog.refreshToken(
storedRefreshToken,
CLIENT_ID,
CLIENT_SECRET
);
// fresh.access_token → new token, valid for 1 hour Error handling
All errors throw InboxDogError with machine-readable codes:
import { InboxDog, InboxDogError } from "inbox.dog";
try {
const tokens = await dog.exchangeCode(code, CLIENT_ID, CLIENT_SECRET);
} catch (e) {
if (e instanceof InboxDogError) {
console.error(e.code); // "INSUFFICIENT_CREDITS"
console.error(e.message); // "No credits remaining. Current balance: 0"
console.error(e.action); // "Purchase credits via POST /api/checkout"
}
} Error codes: INVALID_CREDENTIALS (401), INSUFFICIENT_CREDITS (402), VALIDATION_ERROR (400), AUTH_CODE_NOT_FOUND (400), TOKEN_EXCHANGE_FAILED (500). See full error reference.
Available scopes
| Scope | Permission |
|---|---|
| Read-only access (default) | |
| email:read | Read-only access |
| email:send | Send emails only |
| email:full | Full access (read, send, modify) |
Using the npm package
The inbox.dog npm package is a zero-dependency TypeScript client. It works in Node.js, Deno, Bun, and Cloudflare Workers.
npm install inbox.dog If you prefer raw HTTP, every method maps to a single fetch call. See the API reference.
inbox.dog vs. doing it yourself
| DIY Google OAuth | inbox.dog | |
|---|---|---|
| Setup time | 1-3 hours | 2 minutes |
| Google Cloud console | Required | Not needed |
| App verification | Required for >100 users | Handled by inbox.dog |
| Token management | You build it | You store tokens, we handle the flow |
| Cost | Free (your time) | $0.10/OAuth flow, refresh free |
| Self-host option | N/A | MIT license, full source available |
Get started
- Follow the full tutorial (5 minutes)
- API reference
- Next.js guide / SvelteKit guide
- View source on GitHub (MIT)