All posts
testing engineering

Why Real Email Beats Mocked SMTP in Your Test Suite

Mocked email gives you a green test. Real email gives you confidence. Here's the gap between them — and why it matters at the worst possible moment.

MailFork Team ·

Your test suite is green. The email tests pass. Then a user reports they never received their password reset link — and when you dig in, you find the MIME boundary in your multipart message was malformed. Every major email client silently discarded it. Your mock never noticed.

This is the core failure mode of mocked email: it validates code structure, not email delivery.

What mocks actually test

A mock SMTP library intercepts the call to your email service, captures the arguments, and lets you assert against them:

expect(mockTransport.sendMail).toHaveBeenCalledWith(
  expect.objectContaining({ to: '[email protected]' })
);

This confirms that your code called sendMail. It says nothing about whether the email would be deliverable, parseable, or renderable by a real client.

The gap

Here are five things that fail in production and never fail in mocks:

1. MIME boundary corruption Multipart messages require precise boundary strings in headers and body. Generate a slightly malformed one and the email arrives as garbage — or doesn’t arrive at all.

2. Attachment encoding Base64-encoded attachments that are line-wrapped incorrectly get corrupted in transit. Your mock accepts the buffer. Real clients download a broken file.

3. DKIM signature failure If your DKIM key or signing logic has a bug, real MTAs will reject or junk your email. Your mock will call it delivered.

4. HTML entity escaping An unescaped & in your HTML body is technically invalid. Some clients handle it gracefully; others strip the affected block. Your mock sees the string you passed in, not what the client renders.

5. Link click tracking If you wrap links with a tracker URL and the redirect chain breaks, users can’t complete their flow. Your mock validated the original URL, not the wrapped one.

Real email as the integration layer

The fix isn’t to write more comprehensive mocks — it’s to use a real SMTP target for integration tests. A dedicated test inbox accepts delivery exactly as a user inbox would. Your CI pipeline sends to it, waits for it, and reads from it via API.

The flow looks like this:

// send the email via your app's normal code path
await request(app).post('/auth/forgot-password').send({ email: alias.address });

// wait for delivery — typically under 2 seconds
const email = await mf.inboxes.waitForEmail({ to: alias.address, timeout: 10000 });

// extract and follow the link
const resetLink = extractLink(email.body.html, '/reset-password');
await request(app).post(resetLink).send({ password: 'new-secret' });

Every step uses real code, real SMTP delivery, and real email parsing. If any of the five failure modes above occurs, the test fails where the bug is — not in production.

The cost of getting this right

The main objection to real email in tests is setup overhead. “Spinning up real SMTP infrastructure for a test suite is impractical.” That was true when you had to manage your own mail server.

MailFork makes the test inbox a one-line call. You get a real address, real delivery, and a polling API. The infrastructure is shared across your team and cleaned up automatically. The overhead is the same as any other API call in your test setup.

If you’re already testing against a real database and a real HTTP server, there’s no principled reason to mock the email layer. Get started with MailFork and close the gap.

Try MailFork

Real email infrastructure for QA teams. Free tier includes 1 inbox and 500 emails/month.

Get started free