Skip to main content
Open Source · MIT License · Rust

Local auth testing without the flaky inbox dance

Pine Mail catches email OTPs, magic links, password resets, and SMS 2FA codes for local development, Playwright tests, CI pipelines, and AI agents. One tiny Rust binary — long-polling waits, Twilio webhooks, and structured signal extraction.

  • SMTP mail catcher + SMS catcher
  • OTP and magic-link extraction
  • 14 native MCP tools for agents

Built for agents & CI-first development

Why developers switch to Pine Mail

It replaces the messy parts of auth testing with one local tool: email testing, SMS 2FA testing, webhook capture, OTP extraction, and agent-readable APIs in the same process.

  • For local dev

    A MailHog and Mailpit alternative that understands modern auth

    Catch SMTP like the classic tools, then go further: parse verification codes, magic links, reset URLs, and SMS messages without asking your app or your browser to pretend.

  • For E2E and CI

    Deterministic Playwright auth tests with zero sleep()

    Long-polling waits return when the matching email or text arrives. Use since , assert the extracted code, clean the inbox, and keep the pipeline boring.

  • For AI agents

    MCP-native email and SMS tools for full MFA flows

    Agents can wait for email, extract a link, wait for SMS, read the OTP, and finish login through 14 structured MCP tools instead of screen-scraping webmail.

The problem: auth testing is still too fragile

Your test suite signs up a user. The app sends an email verification code, then a text-message 2FA code. The whole thing should take seconds, but it often turns into waits, retries, inbox scraping, and one very suspicious timeout.

  • Cloud sandboxes add coupling

    Mailtrap, SendGrid, and phone-verification SaaS reduce the blast radius, but your local suite still depends on the network, API keys, account state, and a pricing tier.

  • Classic catchers are email-only

    MailHog and Mailpit are great SMTP catchers, but they stop at email. Modern auth testing needs SMS, OTP extraction, magic links, and clean wait APIs too.

  • Agents hit a wall at OTPs

    An autonomous agent can't casually read a phone or browse webmail like a patient human. It needs a native, structured channel for email and SMS.

How it works

One binary, two channels, one long-polling engine. Point your app at the listeners — your tests and agents just wait and read.

Producer

Your app

  • SMTP · :1025 email triggers
  • Twilio → /api/sms/webhook form + JSON parsing
  • REST · POST /api/sms direct push
emails & SMS
Core engine

Pine Mail

single binary · Rust · SQLite
  • Email store MIME & HTML · auto signal extraction
  • SMS store JSON & Twilio · auto signal extraction
activity inbox 0
  1. SMTP 0421 · received
  2. extract → OTP 812904
  3. analysis · spam 0.02
  4. delivered → agent 7712
  5. mailbox idle · 0 waiting
structured JSON
Consumer

Tests & agents

  • 14 MCP tools native stdio server
  • /api/wait · /api/sms/wait long-poll, zero sleep()
  • Web UI · :8025 dual-tab dashboard
  1. 1

    Point your app at Pine Mail

    SMTP relay at localhost:1025 ; Twilio webhook at /api/sms/webhook .

  2. 2

    It catches and parses everything

    Stores messages in SQLite and extracts codes, magic links, and reset URLs automatically.

  3. 3

    Your test or agent waits, then reads

    Long-poll /api/wait or /api/sms/wait , pull the OTP, keep going. Zero sleep().

Everything you need, nothing you don't

Four capabilities that make Pine Mail feel boring in the best developer-product sense: predictable, local, scriptable, and easy to delete from your mental stack.

Long-polling wait API

/api/wait and /api/sms/wait hold the request open until the matching message arrives. Use since timestamps so fast delivery never slips through the gap.

Signal extraction

Pull OTP codes, email verification links, magic login links, and reset URLs out of email bodies and SMS text. Structured JSON — no regex, no MIME debugging.

SMS + Twilio webhooks

Catch texts via JSON or a native Twilio webhook parser. Full SMS 2FA flows locally — no phone, no SMS gateway in your test path.

Email analysis

Run an 11-factor HTML email compatibility check plus a SpamAssassin-style score. Assert template quality before a broken transactional email ships.

Developer guide

Plain HTTP with JSON responses on localhost:8025 . No SDK, no auth, no API keys — just curl, or whatever your test suite already speaks.

Base URL http://localhost:8025

Conventions

  • Long-polling waits

    /api/wait and /api/sms/wait hold the request open and resolve the instant a matching message lands. On timeout they return a clean 408 — no hangs, no polling loops.

  • Race-free since

    Both wait endpoints accept an ISO-8601 since timestamp. Capture it before triggering the action, and a message that lands before your fetch connects still gets returned ( received_at >= since ).

  • Error codes

    400 for invalid parameters, 404 for unknown message IDs, 408 on wait timeout.

Email

GET /api/wait long-poll

Wait for an email matching the filters, then return it.

curl "http://localhost:8025/api/[email protected]&subject=Verify&since=2026-09-04T10:00:00Z&timeout_ms=15000"
Query param Type Required Description
to string no Filter by recipient address.
subject string no Filter by subject substring.
since ISO-8601 no Only messages with received_at >= since .
timeout_ms int no Max wait before returning 408 .
Response — the full message payload
{
  "id": "msg_01J6XYZ",
  "from": "[email protected]",
  "to": "[email protected]",
  "subject": "Confirm your account",
  "text": "Your verification code is 849201",
  "html": "<html><body>...</body></html>",
  "received_at": "2026-09-04T10:00:01.123Z"
}

GET /api/messages/:id fetch

Fetch a single stored email by ID, including the raw HTML body.

curl "http://localhost:8025/api/messages/msg_01J6XYZ"

GET /api/messages/:id/extract signals

Pull verification codes, magic login links, and password reset URLs into structured JSON. Recognizes 4&ndash;8 digit numeric and alphanumeric OTPs.

curl "http://localhost:8025/api/messages/msg_01J6XYZ/extract"

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

GET /api/messages/:id/analysis CI assertions

11-factor compatibility check for legacy clients plus a heuristic spam score. Deterministic enough to assert against in CI before merge.

curl "http://localhost:8025/api/messages/msg_01J6XYZ/analysis"

{
  "spam_score": 12,
  "issues": ["missing doctype", "un-inlined styles"]
}

Cleanup: POST /api/messages/bulk-delete and PATCH /api/messages/bulk-read reset or mark inbox state between test runs.

SMS

POST /api/sms ingest

Push a structured SMS straight from a test driver or notification service.

curl -X POST "http://localhost:8025/api/sms" \
  -H "Content-Type: application/json" \
  -d '{"from": "+15550199", "to": "+15550100", "body": "Your code is 839201"}'

POST /api/sms/webhook Twilio

Point your app's Twilio client or webhook dispatcher here. Standard x-www-form-urlencoded fields ( From , To , Body ) are parsed automatically.

curl -X POST "http://localhost:8025/api/sms/webhook" \
  -d "From=%2B15550199" -d "To=%2B15550100" \
  -d "Body=Your verification code is 839201"

GET /api/sms/wait long-poll

Long-poll for an SMS sent to a number. Same since and timeout_ms semantics as email.

curl "http://localhost:8025/api/sms/wait?to=%2B15550100&since=2026-09-04T10:00:00Z&timeout_ms=10000"

GET /api/sms/:id/extract signals

Extract the OTP and any embedded URLs from an SMS body.

curl "http://localhost:8025/api/sms/sms_01A2B3/extract"

{ "codes": ["839201"], "links": [] }

Cleanup: POST /api/sms/bulk-delete and PATCH /api/sms/bulk-read .

Pine Mail vs. MailHog, Mailpit, and Mailtrap

Honest comparison. MailHog and Mailpit are still useful. Mailtrap is polished. Pine Mail is built for the local auth-testing jobs they were never quite designed to own.

Feature Pine Mail MailHog Mailpit Mailtrap
Email catching &#10003; &#10003; &#10003; &#8764;
SMS catching + Twilio webhooks &#10003; &#10007; &#10007; &#8764;
Long-polling wait API ( /api/wait , /api/sms/wait ) &#10003; &#10007; &#8764; &#10007;
Signal extraction (OTP / magic links) &#10003; &#10007; &#10007; &#10007;
Litmus-style email analysis &#10003; &#10007; &#10007; &#10007;
14 MCP tools for AI agents &#10003; &#10007; &#10007; &#10007;
Single static binary (<15 MB) &#10003; &#10007; &#10003; &#10007;
Free & open source (MIT) &#10003; &#10003; &#10003; &#10007;

Running locally in under 30 seconds

Same binary, several install paths. Start with Docker, Homebrew, Cargo, or the one-liner and point your app at localhost:1025 .

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

Pulls the image from Docker Hub and starts in one shot — SMTP on 1025 , dashboard on 8025 .

Built for AI agents

A native Model Context Protocol (MCP) server exposes the whole local inbox as 14 tools — 7 for email, 7 for SMS. An agent can complete signup, email verification, password reset, and SMS 2FA without touching a browser inbox.

MCP config — Claude Desktop, Cursor, Windsurf, Claude Code
{
  "mcpServers": {
    "pinemail": {
      "command": "pinemail-mcp",
      "env": {
        "PINEMAIL_URL": "http://localhost:8025"
      }
    }
  }
}

Email tools

  • wait_for_email

    Long-poll until a matching email arrives.

  • extract_signals

    Pull OTP codes and URLs from an email.

  • list_emails

    Query emails with search & pagination.

  • get_email

    Full HTML, text, and attachment data.

  • send_test_email

    Inject a synthetic test email.

  • delete_email

    Delete a specific email by ID.

  • clear_inbox

    Reset email state between runs.

SMS tools

  • wait_for_sms

    Long-poll until a matching SMS arrives.

  • extract_sms_signals

    Pull OTP codes and links from an SMS.

  • list_sms

    Query captured SMS with pagination.

  • get_sms

    Get a single SMS message details.

  • send_test_sms

    Inject a synthetic test SMS.

  • delete_sms

    Delete a specific SMS by ID.

  • clear_sms_inbox

    Reset SMS state between runs.

One prompt, a full multi-factor flow

  1. 01

    Agent signs up a user in the browser.

  2. 02

    Call wait_for_email extract_signals → open the confirmation link.

  3. 03

    Call wait_for_sms extract_sms_signals → grab the OTP.

  4. 04

    Submit the code, complete login, and assert the dashboard. Seconds, not flaky minutes.

In your test suite

Deterministic, race-free, and fast. If delivery fails, the test fails on a clean timeout — it never hangs and never waits on someone else's API.

Node.js · email verification
const since = new Date().toISOString();   // capture BEFORE triggering

await fetch("http://localhost:3000/api/signup", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "[email protected]", password: "..." }),
});

const email = await (await fetch(
  `http://localhost:8025/api/[email protected]&since=${since}&timeout_ms=10000`
)).json();

const { codes } = await (await fetch(
  `http://localhost:8025/api/messages/${email.id}/extract`
)).json();

await fetch("http://localhost:3000/api/verify-email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "[email protected]", code: codes[0] }),
});
console.log("verified with code", codes[0]);
  • <15 MB

    Single binary

  • <5 ms

    Startup time

  • 2

    Channels: email + SMS

  • 14

    MCP tools for AI agents

Frequently asked questions

Does Pine Mail need Docker, Mailtrap, or a cloud account?

Neither. It's a single static binary with embedded SQLite and an embedded web UI. Docker is one convenient install option among several — Homebrew, one-liners, and Cargo all work too.

Is Pine Mail a MailHog or Mailpit alternative?

Yes, if you want a local SMTP mail catcher with a sharper testing API. Pine Mail catches email like those tools, but also adds SMS catching, Twilio webhook parsing, OTP and magic-link extraction, race-free long-polling waits, email analysis, and MCP tools for agents.

How does SMS catching actually work?

Two ways: POST structured JSON to /api/sms , or point your app's Twilio client at /api/sms/webhook — Pine Mail parses standard form payloads (From/To/Body) automatically. Then long-poll /api/sms/wait and extract OTPs via /api/sms/:id/extract .

Can AI agents really use this?

Yes. The pinemail-mcp server exposes 7 email and 7 SMS tools over Model Context Protocol. Configure it once in Claude Desktop, Cursor, Windsurf, or Claude Code, and agents can wait, extract, and assert — all natively.

Is it safe to run in CI?

Yes. Pass --memory for an in-memory database, long-poll with a timeout (clean 408 on miss), and use the bulk read/delete endpoints to reset between tests. No temp files left on disk.

Will it accidentally send real emails or relay spam?

No. It's a dead-end local SMTP listener with no auth, no relaying, and no external calls. That's exactly what makes it safe for test environments.

Make auth testing boring again.

Pine Mail catches email OTPs, magic links, password resets, and SMS 2FA codes — free, open source under the MIT license, running locally in under 30 seconds.

Let's build something that scales.

Planning an AI product, automating a workflow, or taking a platform to production? I'd love to hear what you're building.