Install
openclaw skills install mailgun-apiMailgun 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 (https://clawhub.ai/byungkyu/api-gateway).
openclaw skills install mailgun-apiAccess the Mailgun API with managed OAuth authentication. Send transactional emails, manage domains, routes, templates, mailing lists, suppressions, and webhooks.
# List domains
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.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
https://api.maton.ai/mailgun/v3/{resource}
Maton 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).
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"
Manage your Mailgun OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.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
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'mailgun'}).encode()
req = urllib.request.Request('https://api.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
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.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": "{connection_id}",
"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.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.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
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://api.maton.ai/mailgun/v3/domains')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
Important: Mailgun API uses application/x-www-form-urlencoded for POST/PUT requests, not JSON.
GET /mailgun/v3/domains
Returns all domains for the account.
GET /mailgun/v3/domains/{domain_name}
POST /mailgun/v3/domains
Content-Type: application/x-www-form-urlencoded
name=example.com&smtp_password=supersecret
DELETE /mailgun/v3/domains/{domain_name}
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:)POST /mailgun/v3/{domain_name}/messages.mime
Content-Type: multipart/form-data
to=recipient@example.com&message=<MIME content>
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 tagsRoutes are defined globally per account, not per domain.
GET /mailgun/v3/routes
Query parameters:
skip - Number of records to skiplimit - Number of records to returnPOST /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 /mailgun/v3/routes/{route_id}
PUT /mailgun/v3/routes/{route_id}
Content-Type: application/x-www-form-urlencoded
priority=1&description=Updated Route
DELETE /mailgun/v3/routes/{route_id}
GET /mailgun/v3/domains/{domain_name}/webhooks
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 /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
PUT /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
Content-Type: application/x-www-form-urlencoded
url=https://example.com/new-webhook
DELETE /mailgun/v3/domains/{domain_name}/webhooks/{webhook_type}
GET /mailgun/v3/{domain_name}/templates
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 /mailgun/v3/{domain_name}/templates/{template_name}
DELETE /mailgun/v3/{domain_name}/templates/{template_name}
GET /mailgun/v3/lists/pages
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 /mailgun/v3/lists/{list_address}
PUT /mailgun/v3/lists/{list_address}
Content-Type: application/x-www-form-urlencoded
name=Updated Newsletter
DELETE /mailgun/v3/lists/{list_address}
GET /mailgun/v3/lists/{list_address}/members/pages
POST /mailgun/v3/lists/{list_address}/members
Content-Type: application/x-www-form-urlencoded
address=member@example.com&name=John Doe&subscribed=yes
GET /mailgun/v3/lists/{list_address}/members/{member_address}
PUT /mailgun/v3/lists/{list_address}/members/{member_address}
Content-Type: application/x-www-form-urlencoded
name=Jane Doe&subscribed=no
DELETE /mailgun/v3/lists/{list_address}/members/{member_address}
# 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}
# 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}
# 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}
# 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}
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 forGET /mailgun/v3/{domain_name}/tags
GET /mailgun/v3/{domain_name}/tags/{tag_name}
DELETE /mailgun/v3/{domain_name}/tags/{tag_name}
GET /mailgun/v3/ips
GET /mailgun/v3/ips/{ip_address}
GET /mailgun/v3/domains/{domain_name}/tracking
PUT /mailgun/v3/domains/{domain_name}/tracking/open
Content-Type: application/x-www-form-urlencoded
active=yes
PUT /mailgun/v3/domains/{domain_name}/tracking/click
Content-Type: application/x-www-form-urlencoded
active=yes
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>
GET /mailgun/v3/domains/{domain_name}/credentials
POST /mailgun/v3/domains/{domain_name}/credentials
Content-Type: application/x-www-form-urlencoded
login=alice&password=supersecret
DELETE /mailgun/v3/domains/{domain_name}/credentials/{login}
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).
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://api.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);
import os
import requests
response = requests.post(
'https://api.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())
import os
import requests
response = requests.get(
'https://api.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']}")
import os
import requests
headers = {'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
domain = 'example.com'
# Create route
route_response = requests.post(
'https://api.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://api.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()}")
application/x-www-form-urlencoded for POST/PUT requests, not JSONevent parametercurl -g when URLs contain brackets to disable glob parsingjq, environment variables may not expand correctly. Use Python examples instead.| 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.
| 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 |
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.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
mailgun. For example:https://api.maton.ai/mailgun/v3/domainshttps://api.maton.ai/v3/domainsSandbox domains can only send to authorized recipients. To send emails: