---
title: "OAuth Support"
slug: oauth-support
description: "Build your own authenticated Resend experiences with OAuth 2.1 and PKCE."
created_at: "2026-07-13"
updated_at: "2026-07-13"
image: https://cdn.resend.com/posts/oauth-support.jpg
humans: ["diel-duarte", "gabriel-miranda", "felipe-freitag", "danilo-woznica"]
---

Today, we're adding **OAuth to the Resend API**. Your app opens an authorization URL, the user approves it on Resend's own consent screen, and you get scoped access tokens back. No API keys change hands, and users can revoke access at any time.

Resend implements **OAuth 2.1 with PKCE**, plus [Dynamic Client Registration](/docs/api-reference/oauth/register) for clients like MCP hosts that can't know their deployment details ahead of time.

<blockquote>
 Resend's OAuth API was a cinch to setup. Same full-feature access as using API keys, but more secure and with way better DX.
  <div className="quote-metadata">
    <div className="quote-avatars">
      <div className="quote-avatar quote-avatar-author">
        <img
          alt="Christian Rocha"
          src="/static/avatars/christian-rocha.jpeg"
          width="40"
          height="40"
        />
      </div>
    </div>
    <span className="quote-author">
      <span className="quote-author-name">Christian Rocha</span>
      <span className="quote-author-job">Co-Founder & CEO, Charm</span>
    </span>
  </div>
</blockquote>

## What you can build with it

- **Desktop and CLI tools**: register a loopback client, open the browser, and send on the user's behalf without ever touching their API key.
- **Hosted integrations**: let users click "Connect Resend" in your product and grant scoped access.
- **Agents and MCP hosts**: register dynamically at runtime and request the minimum scope they need.

## Integrations already in the ecosystem

The ecosystem is already growing. Today, we launch with two third-party integrations operational.

### Pop by Charm

[Pop](https://github.com/charmbracelet/pop) is a CLI tool for sending email from your terminal with a TUI or CLI, delivered through Resend, now authenticated through OAuth.

<video
  src="https://cdn.resend.com/posts/oauth-support-2.mp4"
  autoPlay
  loop
  muted
  playsInline
  className="extraWidth"
/>

### Raycast Resend Extension

Raycast is a favorite tool for so many builders, creators, and developers. The [Raycast Resend extension](https://www.raycast.com/resend/resend) now supports OAuth to enable managing API keys, domains, audiences, contacts, and emails without leaving Raycast.

<video
  src="https://cdn.resend.com/posts/oauth-support-1.mp4"
  autoPlay
  loop
  muted
  playsInline
  className="extraWidth"
/>

## How it works

For the full walkthrough, including both remote and local options, see [Building an OAuth client for Resend](/docs/guides/building-a-resend-oauth-client).

### 1. Register your client

Call the [Register Client](/docs/api-reference/oauth/register) endpoint to dynamically register your OAuth client. Request the smallest scope that works:

- `emails:send` covers send-only routes like `POST /emails` and `POST /broadcasts/:id/send`.
- `full_access` is required for every other route.

```bash
curl -X POST 'https://api.resend.com/oauth/register' \
     -H 'Content-Type: application/json' \
     -d '{
  "client_name": "Example OAuth Client",
  "redirect_uris": ["http://127.0.0.1/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "emails:send"
}'
```

The response returns a UUID `client_id`.

### 2. Generate PKCE values and send the user to authorize

Every authorization code exchange requires PKCE, so generate a `code_verifier`, its `code_challenge`, and a `state` value before you redirect.

```javascript
import { createHash, randomBytes } from 'node:crypto';

const base64url = (input) => Buffer.from(input).toString('base64url');

const codeVerifier = base64url(randomBytes(64));
const codeChallenge = base64url(
  createHash('sha256').update(codeVerifier).digest(),
);
const state = base64url(randomBytes(24));
```

Open the authorization URL **in the user's browser** so they see the consent screen. 

### 3. Exchange the code for tokens

Send the original `code_verifier` using the preferred form encoding.

```bash
curl -X POST 'https://api.resend.com/oauth/token' \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -d 'grant_type=authorization_code&client_id=550e8400-e29b-41d4-a716-446655440000&code=AUTHORIZATION_CODE&redirect_uri=http%3A%2F%2F127.0.0.1%3A49152%2Foauth%2Fcallback&code_verifier=CODE_VERIFIER_VALUE'
```

You get back a JWT access token and an opaque refresh token:

```json
{
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6Im9hdXRoX2tleSIsInR5cCI6ImF0K2p3dCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "JcL7aYfE7S9h3L4qv0o2e1w8m6n5b3x9RkP2tD4uV6Q",
  "scope": "emails:send"
}
```

### 4. Call the API with the access token

Pass the access token as a Bearer token to any route your scope allows.

```bash
curl -X POST 'https://api.resend.com/emails' \
     -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6Im9hdXRoX2tleSIsInR5cCI6ImF0K2p3dCJ9...' \
     -H 'Content-Type: application/json' \
     -d '{
  "from": "Acme <onboarding@resend.dev>",
  "to": ["delivered@resend.dev"],
  "subject": "Sent with OAuth",
  "html": "<p>No API key required.</p>"
}'
```

Access tokens expire after 15 minutes. Refresh them with the refresh token, which rotates on every use.

## Users stay in control

Users can review and revoke any app's access from their Resend account's Team settings. 

<video
  src="https://cdn.resend.com/posts/oauth-support-4.mp4"
  autoPlay
  loop
  muted
  playsInline
  className="extraWidth"
/>

You can also revoke a grant yourself by [revoking its refresh token](/docs/api-reference/oauth/revoke).

```bash
curl -X POST 'https://api.resend.com/oauth/revoke' \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -d 'client_id=550e8400-e29b-41d4-a716-446655440000&token=JcL7aYfE7S9h3L4qv0o2e1w8m6n5b3x9RkP2tD4uV6Q&token_type_hint=refresh_token'
```

## Conclusion

OAuth turns Resend into something users can safely plug into any app, and it's the same foundation behind our [remote MCP server](/changelog/remote-mcp-server). 

If you have any questions, please reach out to us and we'll be happy to help.
