Mailgun
Send transactional and marketing email with Mailgun. Use this skill when the
You are an email operations specialist who integrates Mailgun into projects. Mailgun
provides a developer-friendly API for transactional and marketing email with strong
support for mailing lists, inbound routing, and email validation.
## Key Points
- Use recipient variables for batch personalization — more efficient than individual sends
- Validate email addresses before adding to mailing lists
- Use tags on every send for filtering events and analytics
- Set up custom tracking domains for branded click/open tracking
- Use Mailgun routes for inbound email processing instead of polling
- Monitor events dashboard for delivery issues
- Use dedicated IPs when sending 50K+ emails/day
- Sending to mailing lists without unsubscribe handling
- Not checking email validation results before bulk sends
- Using the sandbox domain in production
- Ignoring permanent failure webhooks — address will keep bouncing
- Sending marketing email through transactional infrastructure without separation
## Quick Example
```bash
npm install mailgun.js form-data
```
```typescript
const validation = await mg.validate.get('user@example.com');
if (validation.result === 'undeliverable') {
// Don't send — address is bad
}
```skilldb get email-services-skills/MailgunFull skill: 236 linesMailgun Email Integration
You are an email operations specialist who integrates Mailgun into projects. Mailgun provides a developer-friendly API for transactional and marketing email with strong support for mailing lists, inbound routing, and email validation.
Core Philosophy
API and SMTP parity
Mailgun offers identical functionality through REST API and SMTP. Use the API for modern applications and SMTP for legacy systems. Both support tags, variables, and tracking — choose based on your architecture, not feature needs.
Mailing lists are first-class
Unlike services where you bolt on list management, Mailgun's mailing lists are deeply integrated. You can send to a list address, manage subscribers, and handle unsubscribes natively without external tools.
Routes enable email-driven workflows
Mailgun's route system matches incoming email by recipient or header pattern and forwards, stores, or webhooks the message. This enables reply processing, support ticket creation, and email-driven automation.
Setup
Install
npm install mailgun.js form-data
Initialize
import Mailgun from 'mailgun.js';
import formData from 'form-data';
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
url: 'https://api.mailgun.net', // or https://api.eu.mailgun.net for EU
});
Key Techniques
Simple send
await mg.messages.create('yourdomain.com', {
from: 'App <noreply@yourdomain.com>',
to: ['user@example.com'],
subject: 'Your password reset link',
text: 'Reset: https://...',
html: '<p>Click <a href="...">here</a> to reset.</p>',
'o:tag': ['security', 'password_reset'],
'v:userId': '123',
});
Template send
Create templates in the Mailgun dashboard or via API, then send with variables.
// Create template via API
await mg.domains.domainTemplates.create('yourdomain.com', {
name: 'password-reset',
description: 'Password reset email',
template: '<p>Hi {{name}}, click <a href="{{resetUrl}}">here</a></p>',
});
// Send with template
await mg.messages.create('yourdomain.com', {
from: 'App <noreply@yourdomain.com>',
to: ['user@example.com'],
subject: 'Reset your password',
template: 'password-reset',
'h:X-Mailgun-Variables': JSON.stringify({
name: 'Alice',
resetUrl: 'https://...',
}),
});
Batch send with recipient variables
Send personalized email to many recipients in one API call (up to 1,000).
await mg.messages.create('yourdomain.com', {
from: 'App <noreply@yourdomain.com>',
to: ['user1@example.com', 'user2@example.com'],
subject: 'Your weekly summary, %recipient.name%',
html: '<p>Hi %recipient.name%, here is your summary...</p>',
'recipient-variables': JSON.stringify({
'user1@example.com': { name: 'Alice', id: '1' },
'user2@example.com': { name: 'Bob', id: '2' },
}),
});
Scheduled send
await mg.messages.create('yourdomain.com', {
from: 'App <noreply@yourdomain.com>',
to: ['user@example.com'],
subject: 'Trial ending tomorrow',
html: trialEndingHtml,
'o:deliverytime': 'Thu, 25 Dec 2025 09:00:00 UTC',
});
Mailing lists
// Create list
await mg.lists.create({
address: 'newsletter@yourdomain.com',
name: 'Newsletter',
access_level: 'readonly',
});
// Add member
await mg.lists.members.createMember('newsletter@yourdomain.com', {
address: 'user@example.com',
name: 'Alice',
vars: JSON.stringify({ plan: 'pro' }),
subscribed: true,
});
// Send to list
await mg.messages.create('yourdomain.com', {
from: 'Updates <updates@yourdomain.com>',
to: ['newsletter@yourdomain.com'],
subject: 'What shipped this week',
html: newsletterHtml,
});
Email validation
Verify addresses before sending to reduce bounces.
const validation = await mg.validate.get('user@example.com');
if (validation.result === 'undeliverable') {
// Don't send — address is bad
}
Inbound routes
// Create a route that forwards matching inbound email to your webhook
await mg.routes.create({
priority: 0,
expression: 'match_recipient("support@yourdomain.com")',
action: ['forward("https://yourdomain.com/api/webhooks/mailgun-inbound")', 'store()'],
description: 'Support inbox',
});
Webhook Processing
| Event | Action |
|---|---|
delivered | Mark delivered in message log |
failed (permanent) | Suppress address |
failed (temporary) | Log, monitor for repeated failures |
complained | Suppress from non-essential sends |
opened | Update engagement metrics |
clicked | Update engagement metrics |
unsubscribed | Update subscription state |
export async function POST(req: Request) {
const form = await req.formData();
const event = form.get('event') as string;
const recipient = form.get('recipient') as string;
switch (event) {
case 'failed':
const severity = form.get('severity') as string;
if (severity === 'permanent') {
await suppressAddress(recipient, 'hard_bounce');
}
break;
case 'complained':
await suppressAddress(recipient, 'complaint');
break;
case 'unsubscribed':
await updateSubscription(recipient, false);
break;
}
return new Response('OK');
}
EU vs US Region
Mailgun offers EU and US hosting. For GDPR compliance, use the EU region:
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
url: 'https://api.eu.mailgun.net', // EU region
});
Domains must be configured in the matching region.
Best Practices
- Use recipient variables for batch personalization — more efficient than individual sends
- Validate email addresses before adding to mailing lists
- Use tags on every send for filtering events and analytics
- Set up custom tracking domains for branded click/open tracking
- Use Mailgun routes for inbound email processing instead of polling
- Monitor events dashboard for delivery issues
- Use dedicated IPs when sending 50K+ emails/day
Anti-Patterns
- Sending to mailing lists without unsubscribe handling
- Not checking email validation results before bulk sends
- Using the sandbox domain in production
- Ignoring permanent failure webhooks — address will keep bouncing
- Sending marketing email through transactional infrastructure without separation
- Hardcoding API keys instead of using environment variables
Install this skill directly: skilldb add email-services-skills
Related Skills
AWS Ses
Send email at scale with Amazon SES (Simple Email Service). Use this skill when
Brevo
Send transactional and marketing email with Brevo (formerly Sendinblue). Use this
Courier
Send transactional notifications including email with Courier. Use this skill when
Customerio
Send transactional and marketing email with Customer.io. Use this skill when the
Email Deliverability
Optimize email deliverability across any provider. Use this skill when the project
Loops
Send transactional and marketing email with Loops. Use this skill when the project