L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

mailgun

mailgun

Mailgun is an email API for sending, receiving and tracking transactional and bulk email. Use when a user asks to send email through Mailgun, send batch emails with per-recipient variables, set up inbound routes, validate email addresses, or verify Mailgun webhooks for delivery, open, click and bounce events.

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Documentation de la Skill

GitHub

name: mailgun description: >- Mailgun is an email API for sending, receiving and tracking transactional and bulk email. Use when a user asks to send email through Mailgun, send batch emails with per-recipient variables, set up inbound routes, validate email addresses, or verify Mailgun webhooks for delivery, open, click and bounce events. license: Apache-2.0 compatibility: 'Any language (REST API); official Node SDK mailgun.js needs Node 18+' metadata: author: terminal-skills version: 1.1.0 category: business tags: - mailgun - email - api - bulk - marketing

Mailgun

Overview

Mailgun is a developer-focused email API (closed source, hosted) for sending, receiving and tracking email over HTTPS or SMTP. It offers batch sending with per-recipient variables, inbound routes, address validation, event webhooks and logs. Accounts live in one of two regions, US (https://api.mailgun.net) or EU (https://api.eu.mailgun.net); the region is fixed per domain and the API key only works against its own region. Checked against the Mailgun documentation and mailgun.js 14.0.1 (August 2026).

Instructions

Step 1: Prepare a domain

Add a sending domain in the dashboard, preferably a subdomain such as mg.acmeshop.io, and publish the SPF and DKIM DNS records Mailgun shows until the domain verifies. A sandbox domain works without DNS but only delivers to recipients you authorize. Use a separate subdomain for marketing mail so its reputation does not affect receipts and password resets. Keep the Private API key in MAILGUN_API_KEY and the HTTP webhook signing key (a different secret) in MAILGUN_WEBHOOK_SIGNING_KEY.

Step 2: Send with mailgun.js

npm install mailgun.js

Since version 3 the constructor takes a FormData implementation; on Node 18+ the built-in global FormData works, no form-data package needed.

// lib/mailgun.ts
import Mailgun from 'mailgun.js'

const mailgun = new Mailgun(FormData)
export const mg = mailgun.client({
  username: 'api',
  key: process.env.MAILGUN_API_KEY!,
  url: 'https://api.eu.mailgun.net', // omit for a US-region account
})

await mg.messages.create('mg.acmeshop.io', {
  from: 'Acme Shop <noreply@mg.acmeshop.io>',
  to: ['dana.ortiz@fastmail.com'],
  subject: 'Your order #4821 has shipped',
  text: 'Order #4821 shipped today.',
  html: '<h1>Order #4821 has shipped</h1>',
  'o:tag': ['order-shipped'],
  'o:tracking-opens': 'yes',
  'o:tracking-clicks': 'yes',
})

The first argument is the sending domain, not the From address. A resolved promise only means Mailgun accepted the message; delivery is reported by events.

Step 3: Batch sending

Up to 1,000 recipients per call. Pass recipient-variables (JSON keyed by address) or every recipient sees the whole to list; reference values as %recipient.name%.

await mg.messages.create('mg.acmeshop.io', {
  from: 'Acme Shop <news@mg.acmeshop.io>',
  to: ['dana.ortiz@fastmail.com', 'li.wei@protonmail.com'],
  subject: 'Hi %recipient.first%, your points expire soon',
  text: 'You have %recipient.points% points left.',
  'recipient-variables': JSON.stringify({
    'dana.ortiz@fastmail.com': { first: 'Dana', points: 320 },
    'li.wei@protonmail.com': { first: 'Li', points: 90 },
  }),
})

Step 4: Validate addresses

const check = await mg.validate.get('dana.ortiz@fastmail.com')
console.log(check.result, check.risk) // e.g. "deliverable" "low"
if (check.result === 'undeliverable') { /* do not send */ }

Result values include deliverable, undeliverable, do_not_send, catch_all and unknown; risk is low, medium, high or unknown. Validation is a separately metered service, so confirm your plan includes it before running it over a list.

Step 5: Inbound routes

await mg.routes.create({
  priority: 1,
  description: 'Support mailbox to the helpdesk',
  expression: 'match_recipient("support@mg.acmeshop.io")',
  action: ['forward("https://acmeshop.io/inbound/mailgun")', 'stop()'],
})

The domain's MX records must point to Mailgun for routes to receive mail.

Step 6: Webhooks

Configure webhook URLs per event (delivered, opened, clicked, failed, complained, unsubscribed) in the dashboard or API. Each JSON payload has signature (timestamp, token, signature) and event-data. The signature is a hex HMAC-SHA256 of timestamp + token keyed with the webhook signing key.

// routes/webhooks/mailgun.ts
import crypto from 'crypto'

const seenTokens = new Set<string>() // use Redis with a TTL in production

function isValid({ timestamp, token, signature }: { timestamp: string; token: string; signature: string }) {
  const expected = crypto.createHmac('sha256', process.env.MAILGUN_WEBHOOK_SIGNING_KEY!)
    .update(timestamp + token).digest('hex')
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
  return fresh && !seenTokens.has(token) &&
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}

export async function handleMailgunWebhook(req: { body: any }) {
  const { signature, 'event-data': event } = req.body
  if (!isValid(signature)) return { status: 401 }
  seenTokens.add(signature.token)

  switch (event.event) {
    case 'failed':
      if (event.severity === 'permanent') await suppressAddress(event.recipient)
      break
    case 'complained':
    case 'unsubscribed':
      await unsubscribeUser(event.recipient)
      break
    case 'delivered':
      await markDelivered(event.message.headers['message-id'])
      break
  }
  return { status: 200 }
}

Examples

Example 1: Order-shipped email from a Node app

User request: "Send a shipping confirmation from our checkout service with Mailgun. We are on the EU region."

The agent installs mailgun.js, creates lib/mailgun.ts with url: 'https://api.eu.mailgun.net' and the key from MAILGUN_API_KEY, and calls mg.messages.create('mg.acmeshop.io', {...}) with from, to, subject, text, html and o:tag: ['order-shipped']. A successful call returns an object like { id: '<20261003091500.1.4F2A@mg.acmeshop.io>', message: 'Queued. Thank you.' }; the Mailgun log then shows the delivered event.

Example 2: Stop mailing bounced and complaining users

User request: "Hard bounces keep hurting our sender score. Wire Mailgun webhooks into our user table."

The agent adds the signed-webhook handler above on POST /webhooks/mailgun, registers the URL for failed and complained events, and on a permanent failure or complaint sets email_suppressed = true for the recipient. Sending a test event from the dashboard returns 200, and a request with a wrong signature fails authentication.

Guidelines

  • Match the API base URL to the account region; a US key against the EU endpoint fails authentication.
  • Verify every webhook with the signing key, reject stale timestamps and reuse of a token, and compare in constant time.
  • Suppress permanent bounces and complaints; Mailgun also keeps its own suppression lists.
  • Tracking pixels (o:tracking-opens) are unreliable because mail clients preload images; use clicks and deliveries for decisions.
  • Use o:testmode: 'yes' to exercise the API without delivering mail.
  • The free plan is limited (100 emails per day at last check) and plans and prices change; read mailgun.com/pricing instead of hard-coding limits.
  • Never put the private API key in front-end code or the repository.
Skills enregistrées