mailgun
// Mailgun API integration with managed OAuth. Transactional email service for sending, receiving, and tracking emails. Use this skill when users want to send emails, manage domains, routes, templates, mailing lists, or suppressions in Mailgun. For other third party apps, use the api-gateway skill (htt
Mailgun
Access the Mailgun API with managed OAuth authentication. Send transactional emails, manage domains, routes, templates, mailing lists, suppressions, and webhooks.
Quick Start
# List domains
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/mailgun/v3/domains')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Base URL
https://gateway.maton.ai/mailgun/v3/{resource}
Replace {resource} with the actual Mailgun API endpoint path. The gateway proxies requests to api.mailgun.net/v3 (US region) and automatically injects your OAuth token.
Regional Note: Mailgun has US and EU regions. The gateway defaults to US region (api.mailgun.net).
Authentication
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
- Sign in or create an account at maton.ai
- Go to maton.ai/settings
- Copy your API key
Connection Management
Manage your Mailgun OAuth connections at https://ctrl.maton.ai.
List Connections
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections?app=mailgun&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Connection
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'mailgun'}).encode()
req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Get Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "78b5a036-c621-40c2-b74b-276195735af2",
"status": "ACTIVE",
"creation_time": "2026-02-12T02:24:16.551210Z",
"last_updated_time": "2026-02-12T02:25:03.542838Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "mailgun",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
Delete Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Specifying Connection
If you have multiple Mailgun connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/mailgun/v3/domains')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '78b5a036-c621-40c2-b74b-276195735af2')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If omitted, the gateway uses the default (oldest) active connection.
API Reference
Important: Mailgun API uses application/x-www-form-urlencoded for POST/PUT requests, not JSON.
Domains
List Domains
GET /mailgun/v3/domains
Returns all domains for the account.
Get Domain
GET /mailgun/v3/domains/{domain_name}
Create Domain
POST /mailgun/v3/domains
Content-Type: application/x-www-form-urlencoded
name=example.com&smtp_password=supersecret
Delete Domain
DELETE /mailgun/v3/domains/{domain_name}
Messages
Send Message
POST /mailgun/v3/{domain_name}/messages
Content-Type: application/x-www-form-urlencoded
from=sender@example.com&to=recipient@example.com&subject=Hello&text=Hello World
Parameters:
from(required) - Sender email addressto(required) - Recipient(s), comma-separatedcc- CC recipientsbcc- BCC recipientssubject(required) - Email subjecttext- Plain text bodyhtml- HTML bodytemplate- Name of stored template to useo:tag- Tag for trackingo:tracking- Enable/disable tracking (yes/no)o:tracking-clicks- Enable click trackingo:tracking-opens- Enable open trackingh:X-Custom-Header- Custom headers (prefix with h:)v:custom-var- Custom variables for templates (prefix with v:)
Send MIME Message
POST /mailgun/v3/{domain_name}/messages.mime
Content-Type: multipart/form-data
to=recipient@example.com&message=<MIME content>
Events
List Events
GET /mailgun/v3/{domain_name}/events
Query parameters:
begin- Start time (RFC 2822 or Unix timestamp)end- End timeascending- Sort order (yes/no)limit- Results per page (max 300)event- Filter by event type (accepted, delivered, failed, opened, clicked, unsubscribed, complained, stored)from- Filter by senderto- Filter by recipienttags- Filter by tags
Routes
Routes are defined globally per account, not per domain.
List Routes
GET /mailgun/v3/routes
Query parameters:
skip- Number of records to skiplimit- Number of records to return
Create Route
POST /mailgun/v3/routes
Content-Type: application/x-www-form-urlencoded
priority=0&description=My Route&expression=match_recipient(".*@example.com")&action=forward("https://example.com/webhook")
Parameters:
priority- Route priority (lower = higher priority)description- Route descriptionexpression- Filter expression (match_recipient, match_header, catch_all)action- Action(s) to take (forward, store, stop)
Get Route
GET /mailgun/v3/routes/{route_id}
Update Route
PUT /mailgun/v3/routes/{route_id}
Content-Type: application/x-www-form-urlencoded
priority=1&description=Updated Route
Delete Route
DELETE /mailgun/v3/routes/{route_id}
Webhooks
List Webhooks
GET /mailgun/v3/domains/{domain_name}/webhooks
Create Webhook
POST /mailgun/v3/domains/{domain_name}/webhooks
Content-Type: application/x-www-form-urlencoded
id=delivered&url=https://example.com/webhook
Webhook types: accepted, delivered, opened, clicked, unsubscribed, complained, permanent_fail, temporary_fail
Get Webhook
GET /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
Update Webhook
PUT /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
Content-Type: application/x-www-form-urlencoded
url=https://example.com/new-webhook
Delete Webhook
DELETE /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
Templates
List Templates
GET /mailgun/v3/{domain_name}/templates
Create Template
POST /mailgun/v3/{domain_name}/templates
Content-Type: application/x-www-form-urlencoded
name=my-template&description=Welcome email&template=<html><body>Hello {{name}}</body></html>
Get Template
GET /mailgun/v3/{domain_name}/templates/{template_name}
Delete Template
DELETE /mailgun/v3/{domain_name}/templates/{template_name}
Mailing Lists
List Mailing Lists
GET /mailgun/v3/lists/pages
Create Mailing List
POST /mailgun/v3/lists
Content-Type: application/x-www-form-urlencoded
address=newsletter@example.com&name=Newsletter&description=Monthly newsletter&access_level=readonly
Access levels: readonly, members, everyone
Get Mailing List
GET /mailgun/v3/lists/{list_address}
Update Mailing List
PUT /mailgun/v3/lists/{list_address}
Content-Type: application/x-www-form-urlencoded
name=Updated Newsletter
Delete Mailing List
DELETE /mailgun/v3/lists/{list_address}
Mailing List Members
List Members
GET /mailgun/v3/lists/{list_address}/members/pages
Add Member
POST /mailgun/v3/lists/{list_address}/members
Content-Type: application/x-www-form-urlencoded
address=member@example.com&name=John Doe&subscribed=yes
Get Member
GET /mailgun/v3/lists/{list_address}/members/{member_address}
Update Member
PUT /mailgun/v3/lists/{list_address}/members/{member_address}
Content-Type: application/x-www-form-urlencoded
name=Jane Doe&subscribed=no
Delete Member
DELETE /mailgun/v3/lists/{list_address}/members/{member_address}
Suppressions
Bounces
# List bounces
GET /mailgun/v3/{domain_name}/bounces
# Add bounce
POST /mailgun/v3/{domain_name}/bounces
Content-Type: application/x-www-form-urlencoded
address=bounced@example.com&code=550&error=Mailbox not found
# Get bounce
GET /mailgun/v3/{domain_name}/bounces/{address}
# Delete bounce
DELETE /mailgun/v3/{domain_name}/bounces/{address}
Unsubscribes
# List unsubscribes
GET /mailgun/v3/{domain_name}/unsubscribes
# Add unsubscribe
POST /mailgun/v3/{domain_name}/unsubscribes
Content-Type: application/x-www-form-urlencoded
address=unsubscribed@example.com&tag=*
# Delete unsubscribe
DELETE /mailgun/v3/{domain_name}/unsubscribes/{address}
Complaints
# List complaints
GET /mailgun/v3/{domain_name}/complaints
# Add complaint
POST /mailgun/v3/{domain_name}/complaints
Content-Type: application/x-www-form-urlencoded
address=complainer@example.com
# Delete complaint
DELETE /mailgun/v3/{domain_name}/complaints/{address}
Whitelists
# List whitelists
GET /mailgun/v3/{domain_name}/whitelists
# Add to whitelist
POST /mailgun/v3/{domain_name}/whitelists
Content-Type: application/x-www-form-urlencoded
address=allowed@example.com
# Delete from whitelist
DELETE /mailgun/v3/{domain_name}/whitelists/{address}
Statistics
Get Stats
GET /mailgun/v3/{domain_name}/stats/total?event=delivered&event=opened
Query parameters:
event(required) - Event type(s): accepted, delivered, failed, opened, clicked, unsubscribed, complainedstart- Start date (RFC 2822 or Unix timestamp)end- End dateresolution- Data resolution (hour, day, month)duration- Period to show stats for
Tags
List Tags
GET /mailgun/v3/{domain_name}/tags
Get Tag
GET /mailgun/v3/{domain_name}/tags/{tag_name}
Delete Tag
DELETE /mailgun/v3/{domain_name}/tags/{tag_name}
IPs
List IPs
GET /mailgun/v3/ips
Get IP
GET /mailgun/v3/ips/{ip_address}
Domain Tracking
Get Tracking Settings
GET /mailgun/v3/domains/{domain_name}/tracking
Update Open Tracking
PUT /mailgun/v3/domains/{domain_name}/tracking/open
Content-Type: application/x-www-form-urlencoded
active=yes
Update Click Tracking
PUT /mailgun/v3/domains/{domain_name}/tracking/click
Content-Type: application/x-www-form-urlencoded
active=yes
Update Unsubscribe Tracking
PUT /mailgun/v3/domains/{domain_name}/tracking/unsubscribe
Content-Type: application/x-www-form-urlencoded
active=yes&html_footer=<a href="%unsubscribe_url%">Unsubscribe</a>
Credentials
List Credentials
GET /mailgun/v3/domains/{domain_name}/credentials
Create Credential
POST /mailgun/v3/domains/{domain_name}/credentials
Content-Type: application/x-www-form-urlencoded
login=alice&password=supersecret
Delete Credential
DELETE /mailgun/v3/domains/{domain_name}/credentials/{login}
Pagination
Mailgun uses cursor-based pagination:
{
"items": [...],
"paging": {
"first": "https://api.mailgun.net/v3/.../pages?page=first&limit=100",
"last": "https://api.mailgun.net/v3/.../pages?page=last&limit=100",
"next": "https://api.mailgun.net/v3/.../pages?page=next&limit=100",
"previous": "https://api.mailgun.net/v3/.../pages?page=prev&limit=100"
}
}
Use limit parameter to control page size (default: 100).
Code Examples
JavaScript - Send Email
const formData = new URLSearchParams();
formData.append('from', 'sender@example.com');
formData.append('to', 'recipient@example.com');
formData.append('subject', 'Hello');
formData.append('text', 'Hello World!');
const response = await fetch(
'https://gateway.maton.ai/mailgun/v3/example.com/messages',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
}
);
const result = await response.json();
console.log(result);
Python - Send Email
import os
import requests
response = requests.post(
'https://gateway.maton.ai/mailgun/v3/example.com/messages',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'},
data={
'from': 'sender@example.com',
'to': 'recipient@example.com',
'subject': 'Hello',
'text': 'Hello World!'
}
)
print(response.json())
Python - List Domains
import os
import requests
response = requests.get(
'https://gateway.maton.ai/mailgun/v3/domains',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
domains = response.json()
for domain in domains['items']:
print(f"{domain['name']}: {domain['state']}")
Python - Create Route and Webhook
import os
import requests
headers = {'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
domain = 'example.com'
# Create route
route_response = requests.post(
'https://gateway.maton.ai/mailgun/v3/routes',
headers=headers,
data={
'priority': 0,
'description': 'Forward to webhook',
'expression': 'match_recipient("support@example.com")',
'action': 'forward("https://myapp.com/incoming-email")'
}
)
print(f"Route created: {route_response.json()}")
# Create webhook
webhook_response = requests.post(
f'https://gateway.maton.ai/mailgun/v3/domains/{domain}/webhooks',
headers=headers,
data={
'id': 'delivered',
'url': 'https://myapp.com/webhook/delivered'
}
)
print(f"Webhook created: {webhook_response.json()}")
Notes
- Mailgun uses
application/x-www-form-urlencodedfor POST/PUT requests, not JSON - Domain names must be included in most endpoint paths
- Routes are global (per account), not per domain
- Sandbox domains require authorized recipients for sending
- Dates are returned in RFC 2822 format
- Event logs are stored for at least 3 days
- Stats require at least one
eventparameter - Templates use Handlebars syntax by default
- IMPORTANT: When using curl commands, use
curl -gwhen URLs contain brackets to disable glob parsing - IMPORTANT: When piping curl output to
jq, environment variables may not expand correctly. Use Python examples instead.
Rate Limits
| Operation | Limit |
|---|---|
| Sending | Varies by plan |
| API calls | No hard limit, but excessive requests may be throttled |
When rate limited, implement exponential backoff for retries.
Error Handling
| Status | Meaning |
|---|---|
| 400 | Bad request or missing Mailgun connection |
| 401 | Invalid or missing Maton API key |
| 403 | Forbidden (e.g., sandbox domain restrictions) |
| 404 | Resource not found |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Mailgun API |
Troubleshooting: API Key Issues
- Check that the
MATON_API_KEYenvironment variable is set:
echo $MATON_API_KEY
- Verify the API key is valid by listing connections:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Troubleshooting: Invalid App Name
- Ensure your URL path starts with
mailgun. For example:
- Correct:
https://gateway.maton.ai/mailgun/v3/domains - Incorrect:
https://gateway.maton.ai/v3/domains
Troubleshooting: Sandbox Domain Restrictions
Sandbox domains can only send to authorized recipients. To send emails:
- Upgrade to a paid plan, or
- Add recipient addresses to authorized recipients in the Mailgun dashboard