Mailtrap Email Testing: Setup, API, and CI Integration

Mailtrap Email Testing: Setup, API, and CI Integration

Mailtrap is a cloud-based email testing service that captures emails sent from your application and exposes them via a web UI and REST API. Unlike self-hosted tools, Mailtrap handles the SMTP infrastructure for you.

How Mailtrap Works

Mailtrap provides a sandbox SMTP server. Configure your application to send emails to Mailtrap's SMTP endpoint. Emails are captured in an inbox you can inspect via UI or API — they never reach real recipients.

# .env.test
SMTP_HOST=sandbox.smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=your-mailtrap-username
SMTP_PASS=your-mailtrap-password

Getting Credentials

  1. Sign up at mailtrap.io
  2. Go to Email Testing → Inboxes → your inbox → SMTP Settings
  3. Copy username, password, host, and port

Mailtrap supports multiple inboxes — create one per project or environment.

REST API

Mailtrap exposes a REST API to fetch and assert on captured emails programmatically.

# List messages in inbox
curl -H <span class="hljs-string">"Api-Token: YOUR_API_TOKEN" \
  <span class="hljs-string">"https://mailtrap.io/api/accounts/ACCOUNT_ID/inboxes/INBOX_ID/messages"

Node.js Example

const MAILTRAP_API_TOKEN = process.env.MAILTRAP_API_TOKEN
const INBOX_ID = process.env.MAILTRAP_INBOX_ID
const ACCOUNT_ID = process.env.MAILTRAP_ACCOUNT_ID

async function getLastEmail() {
  const res = await fetch(
    `https://mailtrap.io/api/accounts/${ACCOUNT_ID}/inboxes/${INBOX_ID}/messages`,
    { headers: { 'Api-Token': MAILTRAP_API_TOKEN } }
  )
  const messages = await res.json()
  return messages[0] // newest first
}

async function clearInbox() {
  await fetch(
    `https://mailtrap.io/api/accounts/${ACCOUNT_ID}/inboxes/${INBOX_ID}/clean`,
    { method: 'PATCH', headers: { 'Api-Token': MAILTRAP_API_TOKEN } }
  )
}

// In your test
beforeEach(async () => {
  await clearInbox()
})

test('sends welcome email on signup', async () => {
  await registerUser({ email: 'alice@example.com', name: 'Alice' })
  
  // Poll for email (allow delivery time)
  let email
  for (let i = 0; i < 10; i++) {
    email = await getLastEmail()
    if (email) break
    await new Promise(r => setTimeout(r, 500))
  }
  
  expect(email).toBeTruthy()
  expect(email.to_email).toBe('alice@example.com')
  expect(email.subject).toBe('Welcome to MyApp, Alice!')
  expect(email.html_body).toContain('Confirm your email')
})

Python Example

import requests
import time

MAILTRAP_TOKEN = os.environ['MAILTRAP_API_TOKEN']
ACCOUNT_ID = os.environ['MAILTRAP_ACCOUNT_ID']
INBOX_ID = os.environ['MAILTRAP_INBOX_ID']

BASE = f"https://mailtrap.io/api/accounts/{ACCOUNT_ID}/inboxes/{INBOX_ID}"
HEADERS = {"Api-Token": MAILTRAP_TOKEN}

def get_last_email():
    r = requests.get(f"{BASE}/messages", headers=HEADERS)
    messages = r.json()
    return messages[0] if messages else None

def clear_inbox():
    requests.patch(f"{BASE}/clean", headers=HEADERS)

def test_password_reset_email(client):
    clear_inbox()
    client.post('/auth/forgot-password', json={'email': 'bob@example.com'})
    
    email = None
    for _ in range(10):
        email = get_last_email()
        if email:
            break
        time.sleep(0.5)
    
    assert email is not None
    assert email['subject'] == 'Reset your password'
    assert 'reset-token' in email['html_body']

Asserting on Email HTML

const { JSDOM } = require('jsdom')

test('password reset link is valid', async () => {
  await requestPasswordReset('user@example.com')
  const email = await waitForEmail()
  
  const dom = new JSDOM(email.html_body)
  const resetLink = dom.window.document.querySelector('a[data-id="reset-link"]')
  
  expect(resetLink).toBeTruthy()
  const url = new URL(resetLink.href)
  expect(url.pathname).toMatch(/^\/reset-password\//)
  expect(url.searchParams.get('token')).toMatch(/^[a-f0-9]{64}$/)
})

Spam Analysis

Mailtrap includes a spam score for each captured email. Assert that your emails have a low spam score:

test('welcome email has acceptable spam score', async () => {
  await registerUser({ email: 'test@example.com' })
  const email = await waitForEmail()
  
  // Mailtrap spam score — lower is better, <5 is acceptable
  expect(email.spam_report.score).toBeLessThan(5)
})

CI Integration

# .github/workflows/test.yml
env:
  SMTP_HOST: sandbox.smtp.mailtrap.io
  SMTP_PORT: 2525
  SMTP_USER: ${{ secrets.MAILTRAP_USER }}
  SMTP_PASS: ${{ secrets.MAILTRAP_PASS }}
  MAILTRAP_API_TOKEN: ${{ secrets.MAILTRAP_API_TOKEN }}
  MAILTRAP_ACCOUNT_ID: ${{ secrets.MAILTRAP_ACCOUNT_ID }}
  MAILTRAP_INBOX_ID: ${{ secrets.MAILTRAP_INBOX_ID }}

Add each secret in your GitHub repository settings. Use a dedicated Mailtrap inbox for CI — separate from your local dev inbox.

Mailtrap vs Self-Hosted

Mailtrap is a good choice when you want a managed service with a UI for manual debugging. For fully offline or cost-sensitive setups, use Mailpit or MailHog instead — both are self-hosted and have equivalent REST APIs.

The key advantage of Mailtrap: team members can view captured emails in a shared web UI without any local setup. The key limitation: requires network access and API credentials, which adds complexity in air-gapped CI environments.

Read more