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-passwordGetting Credentials
- Sign up at mailtrap.io
- Go to Email Testing → Inboxes → your inbox → SMTP Settings
- 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.