mailpit-testing
Configures and runs Mailpit - modern dev-mailbox server for SMTP testing with built-in REST API for assertions; default SMTP `1025` + Web UI `8025`; ships single static binary or multi-architecture Docker images; features Chaos mode (configurable SMTP errors for resilience testing), message tagging (manual + auto via filters and plus-addressing), search filters. Use when the user develops email-sending code locally / in CI and needs SMTP capture with programmatic test assertions, or when migrating from MailHog (which Mailpit succeeds).
Install with skills.sh (any agent)
npx skills add testland/qa --skill mailpit-testingmailpit-testing
Overview
Per mailpit.axllent.org/docs/ (opens in new window):
"Mailpit is packed full of features for developers wanting to test SMTP and emails. It acts as an SMTP server, provides a modern web interface to view & test intercepted emails."
Per mp-docs (opens in new window) the differentiated features:
Mailpit succeeded MailHog as the de facto OSS dev mailbox in the mid-2020s; new projects start with Mailpit by default.
When to use
Step 1 - Install
Per github.com/axllent/mailpit (opens in new window):
# macOS
brew install mailpit
# Linux + macOS via install script
sudo sh < <(curl -sL https://raw.githubusercontent.com/axllent/mailpit/develop/install.sh)
# Custom install path
sudo INSTALL_PATH=/usr/bin sh < <(curl -sL https://raw.githubusercontent.com/axllent/mailpit/develop/install.sh)Docker is the recommended path for CI; consult mp-gh (opens in new window) for current Docker pull commands.
Step 2 - Start
Per mp-gh (opens in new window):
"The Mailpit web UI listens by default on
http://0.0.0.0:8025and the SMTP port on0.0.0.0:1025."
Foreground:
mailpit
# Web UI: http://localhost:8025
# SMTP: localhost:1025As a background service on macOS:
brew services start mailpitDiscover all options:
mailpit -hStep 3 - Configure your app's SMTP
Point the application's SMTP config at Mailpit:
# example: Rails action_mailer config
smtp_settings:
address: localhost
port: 1025
domain: localhost
authentication: nil # Mailpit accepts unauthenticated SMTP by defaultEquivalent envs work for Django (EMAIL_HOST=localhost, EMAIL_PORT=1025), Spring (spring.mail.host=localhost, spring.mail.port=1025), Node nodemailer, etc.
Step 4 - Assert via REST API
Per mp-docs (opens in new window) Mailpit ships a "REST API for integration testing." The canonical endpoints (consult mp-docs (opens in new window) for current paths per release) follow this shape:
import requests
BASE = "http://localhost:8025"
def test_password_reset_sends_email():
# 1. Clear inbox before the test
requests.delete(f"{BASE}/api/v1/messages")
# 2. Trigger the email send
trigger_password_reset("alice@example.com")
# 3. Poll until the email lands (typical: <1s)
msg = poll_for_message(BASE, to="alice@example.com", timeout=5)
# 4. Assert
assert msg["Subject"] == "Reset your password"
assert "/reset?token=" in msg["Text"]
assert msg["To"][0]["Address"] == "alice@example.com"def poll_for_message(base, to, timeout):
import time
deadline = time.time() + timeout
while time.time() < deadline:
response = requests.get(f"{base}/api/v1/search", params={"query": f"to:{to}"})
messages = response.json().get("messages", [])
if messages:
return requests.get(f"{base}/api/v1/message/{messages[0]['ID']}").json()
time.sleep(0.1)
raise AssertionError(f"No email to {to} within {timeout}s")The exact endpoint paths may evolve - always check the live API schema at http://localhost:8025/api/v1/ (Mailpit ships an OpenAPI schema for self-introspection).
Step 5 - Chaos mode
Per mp-docs (opens in new window): "Chaos feature to enable configurable SMTP errors for testing application resilience."
Use cases for app-level resilience testing:
Per mp-docs (opens in new window), Chaos is configurable per-recipient or globally; consult the live docs for current Chaos API shape.
Step 6 - Tagging + plus-addressing
Per mp-docs (opens in new window): "automated tagging using filtering and 'plus addressing'."
Pattern for test-isolation: each test sends to alice+test-${test_id}@example.com; Mailpit auto-tags by the +test-... suffix; tests filter by tag to isolate their email batch from concurrent test runs:
import uuid
test_id = uuid.uuid4().hex[:8]
to_addr = f"alice+test-{test_id}@example.com"
trigger_email(to_addr)
msg = requests.get(
f"{BASE}/api/v1/search",
params={"query": f'tag:"test-{test_id}"'},
).json()["messages"][0]Step 7 - CI integration
services:
mailpit:
image: axllent/mailpit:latest
ports: [1025:1025, 8025:8025]
steps:
- run: pytest tests/integration/email/ -v
env:
SMTP_HOST: localhost
SMTP_PORT: 1025Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Skip per-test inbox clear | Stale messages cause false-positives | DELETE /api/v1/messages in setup (Step 4) |
| Assume Mailpit handles authenticated SMTP | Default config is unauthenticated; auth tests need explicit config | Configure --smtp-auth-allow-insecure or proper auth config |
| Hardcode message IDs in tests | IDs are random per send; tests fail | Search by recipient/subject (Step 4) |
| Skip polling; assert immediately | Sub-second SMTP delivery isn't guaranteed | Poll with timeout (Step 4) |
| Test only happy path | Misses retry/dead-letter scenarios | Use Chaos mode (Step 5) |
Limitations
References
Related skills
email-flow-test-author
Build-an-X for end-to-end email-flow tests - trigger → SMTP capture (via Mailpit / MailHog) → header assertions (DKIM/SPF/DMARC when relayed via real MTA) → body assertions (HTML + plain-text alternative) → link-rewrite + tracking-pixel handling → unsubscribe-link verification → bounce + complaint testing in non-prod (via Mailtrap-style services). Use when authoring tests for any transactional or marketing email flow regardless of the SMTP capture tool.
in-app-notification-test-author
Build-an-X workflow for testing real-time in-app notifications delivered over WebSocket (RFC 6455) or Server-Sent Events (WHATWG SSE spec), Firebase Realtime Database / Firestore listeners, and notification center read/unread state - covers fan-out to multiple sessions, offline-then-reconnect delivery, and ordering guarantees. Distinct from email, SMS, push, and webhook channels. Use when authoring tests for any notification that appears inside a connected web or mobile app UI without leaving the application.
mailhog-testing
Captures and asserts SMTP email in tests with MailHog, the Go-based dev mailbox (SMTP sink on `1025`, web UI + JSON API on `8025`, single Go binary or Docker), reading captured mail via APIv2 (`/api/v2/messages`, `/api/v2/search`) and injecting failures with the Jim chaos monkey. Use when a project already runs MailHog to test password-reset, verification, or notification emails; for new projects prefer Mailpit (richer API, active maintenance) - migration path in references.
push-notification-test-author
Build-an-X for push notification tests (push notifications, web push, FCM / APNs push messages) across Web Push (RFC 8030 / VAPID), Apple Push Notification Service (APNs), and Firebase Cloud Messaging (FCM) - covers subscription handshake, payload encryption, badge / sound / click-action assertions, expired-subscription cleanup, silent-vs-alert, and topic-vs-targeted routing. Use when authoring tests for any push notification flow.
sms-test-author
Build-an-X for SMS-flow tests - uses Twilio Magic Numbers (`+15005550006` valid recipient, `+15005550001` invalid number, `+15005550002` cannot route, `+15005550003` international restriction, etc.) and Test Credentials for safe assertion-only Twilio interactions; covers segment-counting (GSM-7 vs UCS-2 encoding); rate-limit + opt-out keyword (STOP / HELP / UNSUBSCRIBE) handling; alphanumeric sender vs short-code vs 10DLC differences. Use when authoring tests for any Twilio-backed SMS flow.
webhook-delivery-tester
Build-an-X for webhook delivery + receiver tests per Standard Webhooks (standardwebhooks.com) - HMAC-SHA256 signature verification, retry semantics with exponential backoff + jitter, replay-window check via timestamp tolerance, ordering guarantees, dead-letter handling for permanent failures, content-type + body-encoding fidelity. Use when authoring tests for webhook senders OR receivers in any system (Stripe / Twilio / SendGrid / GitHub / GitLab outbound webhooks; SaaS app inbound webhooks).