Summary
Buggregator already runs a fake SMTP server that captures emails, but the current API (GET /api/events?type=smtp) is too coarse for end-to-end tests. Teams keep asking for the ability to use Buggregator as the mail backend in their E2E suites (Playwright, Cypress, PHPUnit, pytest, etc.) — finding the right message, waiting for it to arrive, and pulling magic links / OTP codes out of it.
This issue proposes a dedicated set of SMTP testing endpoints so Buggregator becomes a first-class drop-in for tools like MailHog / Mailpit / MailCatcher in test pipelines.
Motivation
Typical E2E flows that need this:
- User signs up → wait for verification email → click link.
- User requests password reset → wait for email → extract reset link.
- User logs in with 2FA → wait for email → extract 6-digit OTP.
- Order placed → assert receipt email was sent to the right address with the right subject.
Today users have to write their own polling loops against /api/events, filter by hand on the to/subject fields inside the JSON payload, and regex-parse HTML to pull links and codes. It works, but every team reinvents the same helpers.
Proposed API
All endpoints scoped under /api/smtp/. Project (mailbox) isolation uses the existing project mechanism — recommended pattern is one project per CI run / per test worker.
1. Search
GET /api/smtp/messages
Query parameters:
| Param |
Purpose |
to, from, cc, bcc |
match by address (exact or substring) |
subject, subject_contains, subject_regex |
match by subject |
body_contains |
substring search in text/html |
since, until |
RFC3339 or unix-ms time window — required for ""last email in the past minute"" patterns |
project |
mailbox isolation |
headers[X-Foo]=bar |
match by arbitrary header (useful for trace-id from the test) |
limit, offset, order=asc|desc |
pagination, newest-first by default |
2. Wait (long-poll) — the core endpoint for E2E
GET /api/smtp/messages/wait?to=...&subject_contains=...&since=...&timeout=30s
Server holds the request until a matching message arrives or the timeout expires. Returns 200 with the message, or 408 on timeout. Accepts the same filters as the search endpoint.
since is effectively required — otherwise the test might pick up a leftover email from a previous run. Convenience helper:
GET /api/smtp/cursor → returns server time as a token. Test grabs a cursor before triggering the action, then passes it as since.
WebSocket subscriptions already exist in the hub, but HTTP long-poll is much simpler for most E2E frameworks to consume.
3. Content extractors
These remove the need for HTML regex parsing in tests:
GET /api/smtp/message/{uuid}/links — every link from text+html with anchor text and source part.
GET /api/smtp/message/{uuid}/codes?pattern=\d{6} — extracted codes by regex; ship sensible defaults for 4/6/8-digit OTPs.
GET /api/smtp/message/{uuid}/raw — original RFC822 source.
GET /api/smtp/message/{uuid}/attachments/{name} — fetch attachment binary (verify this exists / works correctly).
4. Cleanup & isolation
DELETE /api/smtp/messages?project=test-run-42 — purge mailbox before/after a test.
- Document the ""project as mailbox"" pattern: pass project via SMTP basic auth (
test-run-42@smtp) so parallel test workers don't pollute each other.
5. Health / introspection
GET /api/smtp/stats?project=... → {count, last_received_at} for smoke checks that the SMTP transport is actually working in CI.
Suggested rollout
- Wait endpoint + extended filters on
/api/smtp/messages — closes ~80% of use cases.
- Link / code extractors — eliminates fragile HTML parsing in tests.
- Project-based isolation docs with examples for Playwright, Cypress, PHPUnit, pytest.
Tradeoffs / open questions
subject_regex and body_contains do a full scan over JSON payload in SQLite. Fine for dev/test scale, but heavy parallel suites with thousands of messages could need indexes on to/subject via generated columns over the JSON payload. Can be deferred.
- Should
project be aliased as inbox in this API surface for terminology that's closer to mail tooling? Not strictly necessary.
- Long-poll vs Server-Sent Events for the wait endpoint — long-poll is simpler and works through every proxy / framework, recommended default.
Prior art
Buggregator already has the right data and the right architecture — this is mostly an API surface addition on top of the existing SMTP module and store.
Summary
Buggregator already runs a fake SMTP server that captures emails, but the current API (
GET /api/events?type=smtp) is too coarse for end-to-end tests. Teams keep asking for the ability to use Buggregator as the mail backend in their E2E suites (Playwright, Cypress, PHPUnit, pytest, etc.) — finding the right message, waiting for it to arrive, and pulling magic links / OTP codes out of it.This issue proposes a dedicated set of SMTP testing endpoints so Buggregator becomes a first-class drop-in for tools like MailHog / Mailpit / MailCatcher in test pipelines.
Motivation
Typical E2E flows that need this:
Today users have to write their own polling loops against
/api/events, filter by hand on theto/subjectfields inside the JSON payload, and regex-parse HTML to pull links and codes. It works, but every team reinvents the same helpers.Proposed API
All endpoints scoped under
/api/smtp/. Project (mailbox) isolation uses the existingprojectmechanism — recommended pattern is one project per CI run / per test worker.1. Search
GET /api/smtp/messagesQuery parameters:
to,from,cc,bccsubject,subject_contains,subject_regexbody_containssince,untilprojectheaders[X-Foo]=barlimit,offset,order=asc|desc2. Wait (long-poll) — the core endpoint for E2E
GET /api/smtp/messages/wait?to=...&subject_contains=...&since=...&timeout=30sServer holds the request until a matching message arrives or the timeout expires. Returns
200with the message, or408on timeout. Accepts the same filters as the search endpoint.sinceis effectively required — otherwise the test might pick up a leftover email from a previous run. Convenience helper:GET /api/smtp/cursor→ returns server time as a token. Test grabs a cursor before triggering the action, then passes it assince.WebSocket subscriptions already exist in the hub, but HTTP long-poll is much simpler for most E2E frameworks to consume.
3. Content extractors
These remove the need for HTML regex parsing in tests:
GET /api/smtp/message/{uuid}/links— every link from text+html with anchor text and source part.GET /api/smtp/message/{uuid}/codes?pattern=\d{6}— extracted codes by regex; ship sensible defaults for 4/6/8-digit OTPs.GET /api/smtp/message/{uuid}/raw— original RFC822 source.GET /api/smtp/message/{uuid}/attachments/{name}— fetch attachment binary (verify this exists / works correctly).4. Cleanup & isolation
DELETE /api/smtp/messages?project=test-run-42— purge mailbox before/after a test.test-run-42@smtp) so parallel test workers don't pollute each other.5. Health / introspection
GET /api/smtp/stats?project=...→{count, last_received_at}for smoke checks that the SMTP transport is actually working in CI.Suggested rollout
/api/smtp/messages— closes ~80% of use cases.Tradeoffs / open questions
subject_regexandbody_containsdo a full scan over JSON payload in SQLite. Fine for dev/test scale, but heavy parallel suites with thousands of messages could need indexes onto/subjectvia generated columns over the JSON payload. Can be deferred.projectbe aliased asinboxin this API surface for terminology that's closer to mail tooling? Not strictly necessary.Prior art
Buggregator already has the right data and the right architecture — this is mostly an API surface addition on top of the existing SMTP module and store.