Skip to content

[FEATURE] SMTP module: API for end-to-end testing (search, wait, extract) #333

@butschster

Description

@butschster

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

  1. Wait endpoint + extended filters on /api/smtp/messages — closes ~80% of use cases.
  2. Link / code extractors — eliminates fragile HTML parsing in tests.
  3. 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.

Metadata

Metadata

Labels

No fields configured for Feature.

Projects

Status
Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions