Skip to main content

Pine Mail — Tiny SMTP Mail Catcher for Developers & AI Agents

Open Source · MIT License

Pine Mail

A tiny, single-binary SMTP mail catcher for developers and AI agents. Catch every email your app sends — locally, instantly, no cloud required.

The absurd problem with testing email

In 2026, we have solved distributed consensus, edge computing, and neural code generation. But testing email verification is still fundamentally broken.

  • Cloud sandbox SaaS

    Mailtrap, SendGrid Sandbox, and others solve the basics — but your local test suite now depends on external network connections, third-party API keys, webhook configurations, and monthly subscription tiers.

  • Classic local catchers

    MailHog and Mailpit are great for human eyes looking at a browser tab. For automated tests you get polling loops, flaky timing, and unparsed MIME payloads that need hours of regex debugging.

  • The AI agent wall

    Autonomous agents driving your test suites can't interact with email cleanly. Asking an LLM to navigate a webmail UI via headless browser is slow, fragile, and expensive.

What Pine Mail actually is

A lightweight, single-binary SMTP mail catcher built specifically for local development, automated test suites, and autonomous AI agents. Point your app's SMTP at localhost:1025. It intercepts every email. View them in a web UI, query via REST API, or connect an AI agent with its native MCP server.

  • <15 MB

    Single binary

  • <5 ms

    Startup time

  • ~12 MB

    RAM usage

  • Zero

    Cloud accounts needed

Quick start

$ docker run -d --name pinemail -p 1025:1025 -p 8025:8025 yoosuf/pinemail:latest

$ git clone https://github.com/yoosuf/pinemail.git && cd pinemail && cargo build --release

No JVM. No Node.js. No Docker required. One file on disk does everything.

Two features that change everything

Pine Mail was built around two specific capabilities designed to eliminate test flakiness and agent friction.

Long-polling wait endpoint

The HTTP request stays open. The instant the SMTP server finishes processing an incoming message, it pushes the payload back and closes the connection. Zero sleep() calls. Zero polling loops. Your assertions run the millisecond the email exists.

Automatic signal extraction

No more parsing MIME boundaries in your test scripts. Pine Mail extracts verification codes, magic links, and password reset URLs automatically. You get clean, structured JSON with exactly what you need.

Long-polling: /api/wait

Instead of juggling sleep timers or polling loops in your test suite, use a single blocking request that resolves the instant the email lands.

Wait up to 15 seconds for an email matching these filters
curl "http://localhost:8025/api/[email protected]&subject=Verify&timeout_ms=15000"

Signal extraction: /api/messages/:id/extract

In 95% of automated test scenarios you don't care about MIME boundaries or CSS styling. You care about the verification code and the reset URL. Pine Mail extracts them for you.

Response
{
  "codes": ["849201"],
  "links": [
    "http://localhost:3000/auth/verify?token=d8f1e09a8b2c4d5e"
  ],
  "action_urls": [
    "http://localhost:3000/auth/verify?token=d8f1e09a8b2c4d5e"
  ]
}

What this looks like in an automated test

Fast, robust, and completely deterministic. If the email delivery fails inside your app, the test fails immediately on the timeout without hanging.

Python
import requests

# 1. Trigger the signup in your application
signup_resp = requests.post("http://localhost:3000/api/signup", json={
    "email": "[email protected]",
    "password": "CorrectHorseBatteryStaple!"
})
assert signup_resp.status_code == 201

# 2. Block until Pine Mail receives the verification email (no sleep needed)
email = requests.get(
    "http://localhost:8025/api/wait",
    params={
        "to": "[email protected]",
        "subject": "Confirm your account",
        "timeout_ms": 10000
    }
).json()

# 3. Pull the OTP code straight out of the parsed signals
extracted = requests.get(f"http://localhost:8025/api/messages/{email['id']}/extract").json()
otp_code = extracted["codes"][0]

# 4. Submit the verification code
verify_resp = requests.post("http://localhost:3000/api/verify-email", json={
    "email": "[email protected]",
    "code": otp_code
})
assert verify_resp.status_code == 200

Native MCP server for AI agents

Model Context Protocol has rapidly become the standard way AI agents interact with local developer tools. Pine Mail ships with a dedicated MCP server binary that exposes the entire inbox as native callable tools.

MCP config
{
  "mcpServers": {
    "pinemail": {
      "command": "pinemail-mcp",
      "env": {
        "PINEMAIL_URL": "http://localhost:8025"
      }
    }
  }
}

Once wired up, your agent has access to seven targeted tools:

  • wait_for_email

    Blocks until a matching email arrives. Zero polling.

  • extract_signals

    Pulls OTP codes and URLs from parsed email content.

  • list_messages

    Queries the inbox with pagination and filters.

  • get_message

    Full HTML, plain text, and MIME data for any message.

  • delete_message

    Clean up a specific message by ID.

  • clear_inbox

    Reset state between test runs. Clean slate, every time.

  • send_test_email

    Injects a simulated test message into the inbox.

Compatible with Claude Desktop, Cursor, Windsurf, Claude Code, and any MCP-capable autonomous framework. No brittle webmail scraping. No custom bash glue. The agent just does the job.

Why Rust?

For developer tooling that sits in the background of your daily workflow, three constraints matter — and Rust nails all three.

  • Zero runtime overhead

    Compiles to a single, statically linked binary. Starts instantly and uses barely 12MB of resident memory. No Docker containers, no language runtimes eating battery.

  • Single-file distribution

    The React web frontend is embedded directly into the binary at compile time via rust-embed. No node_modules, no static asset directories. One file does everything.

  • Resilience under load

    Tokio's async runtime handles hundreds of simultaneous SMTP connections effortlessly. SQLite in WAL mode guarantees fast, reliable persistence without UI freezes.

The codebase is organized as a clean cargo workspace with three focused crates: pinemail-core (SQLite storage, MIME parsing, signal extraction), pinemail-server (SMTP + HTTP API + embedded dashboard), and pinemail-mcp (MCP stdio server). Pass --memory for CI to run with an in-memory database — tests fly, zero temp files left on disk.

What's on the roadmap

Currently at v0.1.0 and actively developed. A few capabilities in progress.

  • Incoming mail webhooks

    Register a webhook endpoint so Pine Mail pushes an HTTP POST payload to your local server the moment a matching message arrives.

  • Rate-limiting simulation

    Simulate real-world email provider throttling and temporary greylisting errors (421/450 responses) to test your application's email queue backoff logic.

  • IMAP read-only interface

    For legacy apps or desktop email clients that insist on fetching mail via IMAP rather than checking a REST API.

Ready to stop fighting your test suite?

Pine Mail is free, open source under the MIT license. Get running in under 30 seconds.