Test email in Docker and CI with a disposable inbox

Mailhog proves an email was sent, not delivered. Test real email from a Docker container or CI job with a disposable inbox over HTTP — create, wait, assert.

✉️ Email Testing ⏱ 7 min read 📅 Updated August 24, 2026
About the Ollastack Engineering Team ✓ Verified

Our content is written by industry practitioners with hands-on experience in enterprise cloud & agentic workflows. We don't write theory — we share what actually works in production.

🧪 Production Labs 🛡️ SOC2 Type II Certified 📖 Developer Playbooks 👥 10M+ Form Submissions

Mailhog proves an email was sent, not delivered. Test real email from a Docker container or CI job with a disposable inbox over HTTP — create, wait, assert.

TL;DR: The Quick Answer

Rather than running complex SMTP catchers inside Docker or CI, test end-to-end email flows using disposable inboxes over HTTP. Provision isolated mailboxes via API, trigger verification emails, long-poll for message arrival, and assert on extracted OTPs or magic links.

Key Takeaways
  • Verify Real Delivery: Local SMTP mocks only test dispatch, while disposable HTTP inboxes validate actual end-to-end delivery and authentication.
  • Zero Docker Plumbing: No need to run mock SMTP containers or expose internal ports; tests communicate over plain HTTPS with an API token.
  • Eliminate Flaky Sleeps: Built-in long-polling wait endpoints return immediately when mail arrives.
  • Parallel Test Isolation: Assign unique inboxes per CI worker to prevent race conditions and mailbox crosstalk.

You containerized your app and now your email tests are awkward: a local SMTP catcher (Mailhog, Mailpit) runs as another service and only proves your app tried to send. To test that email actually delivers and contains the right thing, use a real disposable inbox over HTTP — no SMTP service in your compose file.

Why local SMTP catchers fall short

Mailhog and Mailpit are great for seeing outgoing mail in development. But they intercept SMTP locally, so they can’t tell you whether mail actually:

  • delivers to a real inbox,
  • passes authentication (SPF/DKIM/DMARC),
  • renders the right code or link for a recipient.

For a test that proves the real round-trip, you need a real inbox your container can read over HTTP.

The pattern in a container

Pass an API token as an env var; everything else is HTTP, so there’s nothing to add to docker-compose.yml:

# docker-compose.yml (test)
services:
  app:
    build: .
    environment:
      - OLLASTACK_API_TOKEN=${OLLASTACK_API_TOKEN}

Inside the test (any language — Node shown):

const API = "https://login.ollastack.com";
const h = { Authorization: `Bearer ${process.env.OLLASTACK_API_TOKEN}` };

// 1. disposable inbox
const inbox = await fetch(`${API}/api/mailboxes`, {
  method: "POST", headers: { ...h, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "ci", mode: "test" }),
}).then(r => r.json());

// 2. drive your app with inbox.address (sign up, reset, etc.)
await signup(inbox.address);

// 3. long-poll for the email and assert
const msg = await fetch(`${API}/api/mailboxes/${inbox.id}/wait?timeout=60`, { headers: h })
  .then(r => r.json());
expect(msg.codes[0]).toMatch(/^\d{6}$/);

No SMTP container, no port wiring — just the token. The wait endpoint long-polls, so the test isn’t a flaky sleep, and codes[]/links[] are extracted so you don’t scrape HTML.

In GitHub Actions / GitLab CI

It’s the same HTTP calls from a CI runner — add OLLASTACK_API_TOKEN as a secret:

# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      OLLASTACK_API_TOKEN: ${{ secrets.OLLASTACK_API_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

No service container, no SMTP sink to maintain.

Keep it isolated

  • One inbox per test (or a +tag subaddress) so parallel jobs don’t read each other’s mail.
  • Bulk-clear between runs and set a retention window so test mail purges itself.
  • Test inboxes are never spam-filtered, so a strict transactional email is never dropped from under your assertion.

Frequently Asked Questions

How do I test email from inside a Docker container?

Use a disposable inbox over HTTP rather than a local SMTP catcher: create an inbox via the API, point your app at its address, then long-poll the wait endpoint and assert on the extracted code or link. It needs only an OLLASTACK_API_TOKEN env var — no SMTP service in the compose file.

Why not just use Mailhog or Mailpit?

Mailhog/Mailpit catch SMTP locally, which proves your app tried to send — but not that mail actually delivers, authenticates, or renders for a real recipient. A real disposable inbox tests the true round-trip.

Does it work in GitHub Actions or GitLab CI?

Yes — it's plain HTTP, so it runs from any container or CI runner with just the API token as a secret. No service container to stand up.

Wrapping Up

In Docker and CI environments, skip the local SMTP catcher when you need to validate full email deliverability. A disposable inbox over HTTP gives you end-to-end verification of deliverability, authentication headers, and payload content with a single API token.

Isolate parallel tests by provisioning unique inboxes or subaddresses per worker, and use automatic retention policies to keep CI environments clean.

✦ Start Building Free