Sign inSign up

a2coder/pretend

By a2coder

Updated 3 months ago

Local Resend API emulator with read-only inbox GUI to capture and inspect emails locally.

Image
0

916

a2coder/pretend repository overview

Pretend - Resend Emulator

Short Description

Local development emulator for the Resend API with a read-only inbox GUI. Capture and inspect emails without calling Resend's production servers.


Overview

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.

Features
  • Resend API compatibility — endpoints for /emails, /contacts, /broadcasts with identical request/response shapes
  • Bearer token authentication — secure with RESEND_API_KEY
  • Dark inbox GUI — view received emails with recipient filtering, HTML/plain-text rendering, and deletion
  • Persistent SQLite storage — database survives container restarts via Docker volume
  • Sandboxed HTML rendering — emails render in an iframe with full CSS/HTML5/web-font support; all JavaScript is neutralized for safety
  • Broadcast expansion — sending a broadcast automatically creates one inbox email per contact
  • Admin authentication — password protected with scrypt hashing for extra security

Quick Start

1. Run with Docker Compose
services:
  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.

Example hashing script in node
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.

2. Point Your App to the Emulator
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>",
});

Configuration

Required Environment Variables
VariableDescription
RESEND_API_KEYBearer token for API authentication (any string; doesn't need to be a real Resend key)
ADMIN_USERNAMEUsername for inbox GUI login
ADMIN_PASSWORDScrypt hash of the admin password (must be pre-hashed before starting the container)
SESSION_SECRETRandom string (≥32 chars) to sign session cookies
Optional
VariableDefaultDescription
DATA_DIR/app/.dataSQLite database directory (inside container)
PORT3000HTTP port the server listens on
HOSTNAME0.0.0.0Bind address

Password Hashing

Important: Passwords must be pre-hashed before running the container. Bun scripts are not available inside the Docker image.

Hashing Locally

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.

Docker Approach

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.)


Data Persistence

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

Health & Logs

Check that the emulator is running:

curl -i http://localhost:3300

View logs:

docker compose logs -f resend-emulator

API Endpoints

Emails
  • POST /emails — send an email
  • GET /emails/:id — fetch email details
Contacts
  • POST /contacts — create/upsert a contact
  • GET /contacts — list contacts
Broadcasts
  • POST /broadcasts — send a broadcast (creates one email per contact)

All endpoints are authenticated with the RESEND_API_KEY bearer token.


Architecture

  • Runtime: Node.js 22 (Next.js standalone server)
  • Database: SQLite with Drizzle ORM
  • Frontend: Next.js + React with dark theme UI
  • Security: Scrypt password hashing, secure session cookies, sandboxed iframe rendering

The Docker image does not include Bun or build tools at runtime; only the compiled Next.js application is present.


License & Support

This is a closed-source project. For issues or feature requests, contact the maintainers directly.

Tag summary

Content type

Image

Digest

sha256:ef22600c3

Size

88.5 MB

Last updated

3 months ago

docker pull a2coder/pretend