← Blog

Gmail OAuth in Node.js — The Easy Way

Get Gmail access tokens with 3 API calls. No Google Cloud console required.

The problem with Gmail OAuth

If you've ever tried to add Gmail access to a Node.js app, you know the pain:

  1. Create a Google Cloud project
  2. Enable the Gmail API
  3. Configure the OAuth consent screen (publisher name, scopes, test users...)
  4. Create OAuth 2.0 credentials
  5. Handle the authorization code flow
  6. Exchange codes for tokens
  7. Store and refresh tokens
  8. 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
emailRead-only access (default)
email:readRead-only access
email:sendSend emails only
email:fullFull 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 time1-3 hours2 minutes
Google Cloud consoleRequiredNot needed
App verificationRequired for >100 usersHandled by inbox.dog
Token managementYou build itYou store tokens, we handle the flow
CostFree (your time)$0.10/OAuth flow, refresh free
Self-host optionN/AMIT license, full source available

Get started