Local Resend API emulator with read-only inbox GUI to capture and inspect emails locally.
916
Local development emulator for the Resend API with a read-only inbox GUI. Capture and inspect emails without calling Resend's production servers.
Resend Emulator is a drop-in local server that mirrors the Resend API for development and testing. Point your Resend SDK at http://localhost:3000 and all email operations are captured in a persistent SQLite database with an elegant inbox UI for inspection and debugging.
/emails, /contacts, /broadcasts with identical request/response shapesRESEND_API_KEYservices:
resend-emulator:
image: a2coder/pretend:latest
ports:
- "3000:3000"
environment:
RESEND_API_KEY: ""
ADMIN_USERNAME: admin
ADMIN_PASSWORD: ""
SESSION_SECRET: local-dev-session-secret-please-change
DATA_DIR: /app/.data # must be same as volume mount
volumes:
- resend-data:/app/.data
restart: unless-stopped
volumes:
resend-data:
Fill in RESEND_API_KEY and ADMIN_PASSWORD (a scrypt hash — see "Password Hashing" below), and replace SESSION_SECRET with a real random string before running in anything beyond local dev.
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
const KEYLEN = 64;
export function hashPassword(password: string): string {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(password, salt, KEYLEN).toString("hex");
return `scrypt:${salt}:${hash}`;
}
Then:
docker compose up -d
Visit http://localhost:3300 and sign in with your admin credentials.
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY, {
baseUrl: "http://localhost:3000", // or http://resend-emulator:3000 from docker-compose
});
// All API calls are now captured locally
await resend.emails.send({
from: "[email protected]",
to: "[email protected]",
subject: "Test",
html: "<h1>Hello</h1>",
});
| Variable | Description |
|---|---|
RESEND_API_KEY | Bearer token for API authentication (any string; doesn't need to be a real Resend key) |
ADMIN_USERNAME | Username for inbox GUI login |
ADMIN_PASSWORD | Scrypt hash of the admin password (must be pre-hashed before starting the container) |
SESSION_SECRET | Random string (≥32 chars) to sign session cookies |
| Variable | Default | Description |
|---|---|---|
DATA_DIR | /app/.data | SQLite database directory (inside container) |
PORT | 3000 | HTTP port the server listens on |
HOSTNAME | 0.0.0.0 | Bind address |
Important: Passwords must be pre-hashed before running the container. Bun scripts are not available inside the Docker image.
If you have access to the source code locally:
bun install
bun run scripts/hash-password.ts your-password
This outputs a scrypt hash. Paste that hash into the ADMIN_PASSWORD value in your docker-compose environment: block.
If you only have the Docker image, you can temporarily create a hashing container:
docker run --rm -v $(pwd):/work -w /work \
oven/bun:canary \
bash -c "bun run scripts/hash-password.ts your-password"
(Requires the source code to be mounted.)
The SQLite database is stored in /app/.data inside the container. Use a named volume (resend-data:) to persist data across restarts and rebuilds:
volumes:
- resend-data:/app/.data # Named volume
# OR for direct host access:
# - ./data:/app/.data # Bind mount
Check that the emulator is running:
curl -i http://localhost:3300
View logs:
docker compose logs -f resend-emulator
POST /emails — send an emailGET /emails/:id — fetch email detailsPOST /contacts — create/upsert a contactGET /contacts — list contactsPOST /broadcasts — send a broadcast (creates one email per contact)All endpoints are authenticated with the RESEND_API_KEY bearer token.
The Docker image does not include Bun or build tools at runtime; only the compiled Next.js application is present.
This is a closed-source project. For issues or feature requests, contact the maintainers directly.
Content type
Image
Digest
sha256:ef22600c3…
Size
88.5 MB
Last updated
3 months ago
docker pull a2coder/pretend