Skip to content

Repository files navigation

Pine Mail 🌲📬

A tiny, single-binary SMTP & SMS catcher for development environments — like Mailtrap or Mailpit, but written in Rust with a minimal footprint (~15MB image), structured as a monorepo, and built for agentic development: AI agents (and e2e tests) can discover, wait for, and extract data from captured emails and SMS messages via a REST API or an MCP server.

Tags: mailSMTPSMSmail-catchersms-catchertestinge2eagentsMCPDockerRustemaildevelopment


Monorepo Layout

crates/core/    pinemail-core   — shared SQLite store, MIME parsing, HTML/spam analysis, signal extraction
crates/server/  pinemail        — SMTP (1025) + HTTP API/UI (8025); what you deploy
crates/mcp/     pinemail-mcp    — MCP stdio server exposing emails & SMS to AI agents
apps/web/                       — React + Vite + TS frontend embedded into `pinemail`

For full system architecture, sequence diagrams, database schemas, and design details, see ARCHITECTURE.md.
For runtime integration details for AI agents and test suites, see AGENTS.md.


Feature Comparison

Feature Pine Mail 🌲 Mailhog Mailpit Mailtrap (SaaS)
Single Static Binary ✅ (Rust, ~15MB) ❌ (Go + deps) ✅ (Go) ❌ (Cloud SaaS only)
Dual Email & SMS Catcher Built-in ❌ Email only ❌ Email only 🟡 Partial
Twilio Webhook Ingestion Built-in
Auto Signal Extraction (OTP / Links) Built-in
Long-Polling Wait API (/api/wait & /api/sms/wait) Built-in 🟡 Partial
Litmus-Style Email Analysis Built-in
14 MCP Tools for AI Agents Built-in
Free & Open Source ✅ MIT

Key Features

  • SMTP Email Catcher (:1025): dead-end local SMTP listener (no auth, no relaying, no external network calls).
  • SMS Catcher & Twilio Webhook Support (:8025): ingest SMS via standard JSON (POST /api/sms) or Twilio webhooks (POST /api/sms/webhook with x-www-form-urlencoded payloads).
  • Dual-Tab Web UI & REST API (:8025): clean React interface for viewing both Emails and SMS messages with live WebSocket updates.
  • Rich Message Inspection: view HTML/plain-text bodies, raw headers, download attachments or raw .eml files.
  • Litmus-Style Email Analysis: GET /api/messages/:id/analysis checks 11 HTML email-client compatibility factors (DOCTYPE, tables, inline CSS, fonts, image alt, size clipping) and provides a SpamAssassin-style heuristic spam score.
  • Search, Pagination & Bulk Actions: multi-select grid with bulk mark read/unread, bulk delete, and paginated lazy-loading (50 items per page).
  • Agentic Long-Polling: GET /api/wait and GET /api/sms/wait long-poll server-side for incoming emails or SMS matching filters (to, from, subject, body, since).
  • Signal Extraction Engine: GET /api/messages/:id/extract and GET /api/sms/:id/extract automatically pull OTP codes (4–8 digits) and magic links out of captured emails and SMS bodies.
  • 14 MCP Agent Tools (pinemail-mcp): Model Context Protocol stdio server exposing 7 Email tools and 7 SMS tools directly to Claude Desktop, Copilot, Cursor, and autonomous test runners.
  • SQLite Storage: persistent SQLite storage (/data/pinemail.db or :memory:) with automatic FIFO pruning past MAX_MESSAGES.
  • Zero External Runtime Dependencies: single binary with embedded frontend built via rust-embed.

Installation & Distribution Methods

Pine Mail provides official distribution packages and single static binaries for macOS, Linux, and Windows.

🍺 Homebrew (macOS & Linux)

Install Pine Mail (both pinemail server binary and pinemail-mcp AI agent tool) via Homebrew from yoosuf/homebrew-tap:

# Add the Homebrew tap and install
brew tap yoosuf/tap
brew install pinemail

# Or install directly in a single command:
brew install yoosuf/tap/pinemail

Manage as a background service (macOS & Linux):

# Start Pine Mail as a background service (SMTP on :1025, Web UI & API on :8025)
brew services start pinemail

# Check service status, stop, or restart
brew services info pinemail
brew services stop pinemail
brew services restart pinemail

⚡ Automated One-Liner Installers

Auto-detects OS and CPU architecture, downloads the latest binary release, and places executables in PATH.

macOS & Linux (POSIX Shell):

curl -fsSL https://raw.githubusercontent.com/yoosuf/pinemail/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/yoosuf/pinemail/main/install.ps1 | iex

🐧 Linux Native Packages

  • Debian / Ubuntu (.deb):
    curl -LO https://github.com/yoosuf/pinemail/releases/latest/download/pinemail_amd64.deb
    sudo dpkg -i pinemail_amd64.deb
  • Fedora / RHEL / CentOS (.rpm):
    sudo rpm -i https://github.com/yoosuf/pinemail/releases/latest/download/pinemail.x86_64.rpm
  • Arch Linux (AUR):
    yay -S pinemail-bin

🪟 Windows Package Managers

  • Scoop:
    scoop bucket add pinemail https://github.com/yoosuf/scoop-bucket
    scoop install pinemail
  • Winget (Windows Package Manager):
    winget install PineMail.PineMail
  • Chocolatey:
    choco install pinemail

📦 Pre-Compiled GitHub Release Binaries

Download standalone pre-built binaries from GitHub Releases:

Operating System Architecture Package File
macOS Apple Silicon (aarch64) pinemail-v*-aarch64-apple-darwin.tar.gz
macOS Intel (x86_64) pinemail-v*-x86_64-apple-darwin.tar.gz
Linux x86_64 pinemail-v*-x86_64-unknown-linux-gnu.tar.gz
Linux ARM64 (aarch64) pinemail-v*-aarch64-unknown-linux-gnu.tar.gz
Windows x86_64 pinemail-v*-x86_64-pc-windows-msvc.zip

🦀 Cargo (Rust Workspace)

Install directly via cargo:

cargo install --git https://github.com/yoosuf/pinemail pinemail-server pinemail-mcp

🐳 Docker & Docker Compose

Pull the published multi-arch image (linux/amd64 + linux/arm64) from Docker Hub:

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

Or run via Docker Compose:

docker compose up --build

To run the MCP server against it:

docker compose --profile mcp run --rm mcp

Docker Hub: yoosuf/pinemail — tags latest and 0.1.0.


Agent Integration Examples

REST API (Long-Polling + Signal Extraction)

# 1. Long-poll for a fresh email matching criteria (up to 10s default, max 60s)
curl "http://localhost:8025/api/wait?to=user@example.com&subject=Verify&since=2026-09-04T09:00:00Z"

# 2. Extract OTP codes and links from the received email
curl "http://localhost:8025/api/messages/<MESSAGE_ID>/extract"
# Response: { "codes": ["482913"], "links": ["https://app.test/verify?token=..."] }

# 3. Analyze HTML compatibility & spam score
curl "http://localhost:8025/api/messages/<MESSAGE_ID>/analysis"

# 4. Long-poll for a fresh SMS message
curl "http://localhost:8025/api/sms/wait?to=+15550100&since=2026-09-04T09:00:00Z"

# 5. Extract OTP codes from the received SMS
curl "http://localhost:8025/api/sms/<SMS_ID>/extract"
# Response: { "codes": ["940182"], "links": [] }

Twilio SMS Ingestion Webhook

Point your application or local webhook relay to:

POST http://localhost:8025/api/sms/webhook
Content-Type: application/x-www-form-urlencoded

From=%2B15550199&To=%2B15550100&Body=Your+verification+code+is+839201

Node.js (E2E Test Automation)

Using Node 18+ native fetch to wait for messages and extract OTP verification codes:

// 1. Capture ISO timestamp BEFORE triggering signup / SMS 2FA action
const since = new Date().toISOString();
const userPhone = "+15550199";

// 2. Trigger your application action (e.g. request 2FA SMS code)
await triggerSmsCodeAction({ to: userPhone });

// 3. Long-poll Pine Mail server-side until SMS arrives (blocks up to timeout_ms)
const waitRes = await fetch(
  `http://localhost:8025/api/sms/wait?to=${encodeURIComponent(userPhone)}&since=${since}&timeout_ms=10000`
);
const sms = await waitRes.json();

// 4. Extract 4-8 digit OTP verification code
const extractRes = await fetch(`http://localhost:8025/api/sms/${sms.id}/extract`);
const { codes } = await extractRes.json();
const otpCode = codes[0]; // e.g. "839201"

// 5. Submit extracted OTP code into your test runner / app
await submitOtpCode(otpCode);

REST API Directory

Email Endpoints (/api/messages)

Method Endpoint Description
GET /api/messages List captured emails (search, limit, offset)
GET /api/messages/:id Fetch full email details (headers, text/html, attachments)
GET /api/messages/:id/raw Download raw MIME file (.eml)
GET /api/messages/:id/html Render raw HTML body
GET /api/messages/:id/attachments/:index Download attachment by index
GET /api/messages/:id/extract Extract OTP codes (4-8 digits) and HTTP/HTTPS links
GET /api/messages/:id/analysis HTML email compatibility checks & heuristic spam score
GET /api/wait Server-side long-polling wait (to, from, subject, since, timeout_ms)
PATCH /api/messages/:id/read Toggle read/unread state
DELETE /api/messages/:id Delete email by ID
POST /api/messages/bulk-delete Bulk delete emails by ID list
PATCH /api/messages/bulk-read Bulk update read state
DELETE /api/messages Clear all emails
POST /api/test-email Inject synthetic test email

SMS Endpoints (/api/sms)

Method Endpoint Description
GET /api/sms List captured SMS messages (search, limit, offset)
GET /api/sms/:id Fetch single SMS details
POST /api/sms Ingest SMS via JSON payload
POST /api/sms/webhook Ingest SMS via Twilio form-urlencoded or JSON webhook
GET /api/sms/wait Server-side long-polling wait for SMS (to, from, body, since, timeout_ms)
GET /api/sms/:id/extract Extract OTP codes (4-8 digits) and HTTP/HTTPS links from SMS
PATCH /api/sms/:id/read Toggle read/unread state
DELETE /api/sms/:id Delete SMS by ID
POST /api/sms/bulk-delete Bulk delete SMS by ID list
PATCH /api/sms/bulk-read Bulk update SMS read state
DELETE /api/sms Clear all SMS messages
POST /api/test-sms Inject synthetic test SMS message

Model Context Protocol (MCP) Server (pinemail-mcp)

Pine Mail includes 14 built-in MCP tools for AI agents (Claude Desktop, Copilot, Cursor, agentic E2E tests):

Category Tool Name Parameters Description
Email Tools list_emails search?, limit?, offset? Query emails with search filter and pagination
get_email id Get email headers, text/html content, and attachment metadata
wait_for_email to?, from?, subject?, since_ms?, timeout_ms? Long-poll server-side until matching email arrives
extract_signals id Automatically extract OTP codes and links from email
send_test_email to? Inject synthetic test email
delete_email id Delete email by ID
clear_inbox none Clear all emails
SMS Tools list_sms search?, limit?, offset? Query captured SMS with search filter and pagination
get_sms id Get single SMS details
wait_for_sms to?, from?, body?, since_ms?, timeout_ms? Long-poll server-side until matching SMS arrives
extract_sms_signals id Automatically extract OTP codes and links from SMS body
send_test_sms to?, from?, body? Inject synthetic test SMS
delete_sms id Delete SMS by ID
clear_sms_inbox none Clear all SMS messages

Configuration (Environment Variables)

Variable Default Description
SMTP_PORT 1025 Port the SMTP listener binds to
HTTP_PORT 8025 Port the web UI / REST API binds to
BIND_ADDR 0.0.0.0 Bind address for both listeners
DB_PATH pinemail.db (/data/pinemail.db in Docker) SQLite database file path (:memory: for ephemeral in-memory DB)
MAX_MESSAGES 1000 Oldest messages/SMS are pruned past this count (0 = unlimited)
SMTP_HOSTNAME pinemail Hostname advertised in the SMTP banner
PINEMAIL_URL http://127.0.0.1:8025 (pinemail-mcp only) Base HTTP URL of the running server

Local Development

# Terminal 1 — Frontend hot reload (Vite dev server proxied to Rust API)
cd apps/web && npm install && npm run dev

# Terminal 2 — Backend server (SMTP on :1025, API on :8025)
cargo run -p pinemail-server

Building Release Binaries

cd apps/web && npm install && npm run build && cd ..
cargo build --release --workspace

The frontend static assets must be built to apps/web/dist before building pinemail-server, as they are embedded directly into the binary using rust-embed.


Author

Built by Yoosuf, who also offers fractional CTO services.

About

Notification testing infrastructure for modern application development & AI agents — with Twilio webhooks, long-polling wait API, signal extraction, email analysis, and 14 MCP tools.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages