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.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per github.com/mailhog/MailHog:
"MailHog is an email testing tool for developers" that allows you to "Configure your application to use MailHog for SMTP delivery" and "View messages in the web UI, or retrieve them with the JSON API."
MailHog is legacy as of the mid-2020s - Mailpit (per
mailpit-testing) succeeded it with
richer API + active maintenance. This skill covers MailHog for
existing deployments + provides migration guidance to Mailpit.
For new projects, use mailpit-testing.
1025, web UI + JSON API on 8025.localhost:1025 with SMTP auth disabled.DELETE /api/v1/messages), trigger the action that sends mail, then poll APIv2 (/api/v2/messages or /api/v2/search) until the message lands.Content.Headers.Subject array - see the Worked example.mailhog -invite-jim); when leaving MailHog, follow Migrating to Mailpit.Per mh-gh:
# Go install
go install github.com/mailhog/MailHog@latestDocker:
docker run -d \
--name mailhog \
-p 1025:1025 \
-p 8025:8025 \
mailhog/mailhogPer mh-gh:
| Port | Service |
|---|---|
| 1025 | SMTP server |
| 8025 | HTTP server (UI + APIv1 + APIv2) |
Same pattern as Mailpit (since both expose unauthenticated SMTP on 1025 by default):
smtp:
host: localhost
port: 1025
auth: nonePer mh-gh MailHog has both APIv1 + APIv2; APIv2 is the modern one. Endpoints:
| Endpoint | Use |
|---|---|
GET /api/v2/messages | List captured messages (paginated) |
GET /api/v2/messages?limit=N&start=M | Pagination |
GET /api/v2/search?kind=to&query=alice@x.com | Search by recipient / subject / containing |
DELETE /api/v1/messages | Clear all messages (uses APIv1; APIv2 has no delete) |
The MailHog message structure is more nested than Mailpit's:
Content.Headers.Subject is an array, not the flat Subject
field Mailpit exposes. Walk the nested path or the assertion
silently fails.
Capture and assert a single password-reset email end to end. Clear the mailbox first so a stale message can't satisfy the assertion, trigger the app action, poll APIv2 until the message lands, then assert on its subject and body:
import requests
BASE = "http://localhost:8025"
def test_password_reset_via_mailhog():
requests.delete(f"{BASE}/api/v1/messages") # clear (APIv1 - no APIv2 delete)
trigger_password_reset("alice@example.com") # app under test sends the email
msg = poll_for_message(BASE, to="alice@example.com") # GET /api/v2/search?kind=to&query=...
assert msg["Content"]["Headers"]["Subject"][0] == "Reset your password"
assert "/reset?token=" in msg["Content"]["Body"]poll_for_message retries GET /api/v2/search?kind=to&query=alice@example.com
until a message appears, then returns it. Sub-second SMTP delivery
isn't guaranteed, so never assert immediately after triggering.
Per mh-gh: "Chaos Monkey for failure testing" via the Jim component. Jim is configurable via CLI flags or environment:
mailhog -invite-jim # enables the chaos monkeyOnce Jim is invited, MailHog injects failures (random connection
drops, slow responses) per the configured probabilities.
Configuration flags are documented in mailhog -h; the README
references "Introduction to Jim" for details.
For new test work needing chaos, prefer Mailpit's Chaos mode
(per mailpit-testing Step 5) - it
has richer per-recipient configuration.
services:
mailhog:
image: mailhog/mailhog
ports: [1025:1025, 8025:8025]
steps:
- run: pytest tests/integration/email/ -vFor new projects, and for teams ready to leave MailHog, migrate to
Mailpit (mailpit-testing) - the
actively maintained successor with a flatter API and richer chaos
configuration. The feature-mapping table, the schema-rewrite notes,
and the step-by-step cutover live in
references/migrating-to-mailpit.md.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Start new project on MailHog | Legacy; loses richer Mailpit features | Use Mailpit (Migrating to Mailpit + cross-skill) |
| Use APIv1 for new test code | Deprecated by APIv2 (within MailHog) | APIv2 endpoints (Assert via APIv2) |
| Assume MailHog flat schema | MailHog's Content.Headers.Subject is an array | Walk the nested structure (Worked example) |
| Skip per-test message clear | Same problem as Mailpit Step 4; stale messages | DELETE /api/v1/messages in setup |
| Skip Jim coverage | Same as Mailpit Step 5; misses resilience | Enable Jim or migrate to Mailpit Chaos |
mailpit-testing - successor;
preferred for new projectsemail-flow-test-author -
build-an-X for the full email-sending workflow (works with either
Mailpit or MailHog)