Skip to content

Mailgun

I need to... Guide Summary
Choose between drupal/mailgun, Mailer Plus, or Symfony Mailer Lite Module Decision Use drupal/mailgun 2.1.x as the default for 2026 Drupal sites; choose Mailer Plus only when you need Twig-templated emails, multi-language policies, or signing. Mixing both on the same site causes conflicts.
Set up SPF, DKIM, and DMARC DNS records DNS Setup Set up a subdomain (e.g., mg.example.com) with SPF TXT record, two DKIM CNAMEs via Automatic Sender Security, and a DMARC TXT at the organizational root; start DMARC at p=none and escalate only after monitoring.
Pick the right Mailgun region (US vs EU) Region Selection Default to the US region unless GDPR or contractual requirements mandate EU data residency; the region choice is permanent per domain and a US API key will return 401 Unauthorized if used against the EU endpoint.
Install and enable the Mailgun module Module Installation Require drupal/mailgun:^2.1 and drupal/mailsystem via Composer then enable both; use ^2.1 specifically to get the July 2025 release, and add Key module support via the open patch at issue
Configure settings.php for safe API key handling Settings Configuration Always add mailgun to config_exclude_modules before saving the API key, then inject the key via getenv() in settings.php; skipping config_exclude_modules commits the API key to git on the next drush cex.
Route module emails through Mailgun via system.mail.yml Mail Routing Set system.mail.yml interface.default to mailgun_mail to route all Drupal mail through Mailgun, or use the Mailsystem module to route specific modules while keeping others on php_mail; never edit system.mail.yml without running drush cim or cex.
Configure the module via the admin UI UI Configuration Navigate to /admin/config/services/mailgun and configure the Private/Sending API key, working domain, and API endpoint per environment; use settings.php env-var overrides rather than hardcoding values that differ between dev, stage, and prod.
Store the API key securely API Key Storage Inject the Mailgun API key via getenv() in settings.php as the default method; never commit mailgun.settings.yml to git — add mailgun to config_exclude_modules first and rotate immediately if a key is accidentally committed.
Send transactional emails programmatically Programmatic Sending Implement hook_mail() to set subject/body/headers on $message, then call MailManager::mail() from a service injected with mail.manager; never call the Mailgun PHP SDK directly — it bypasses routing, test mode, and the queue.
Use Mailgun-specific features (tags, tracking, attachments) Mailgun-Specific Params Pass tags, tracking toggles, deliverytime, bcc, and custom headers via $params in MailManager::mail() and copy them to $message['params'] in hook_mail(); Mailgun limits 3 tags per message and rejects deliverytime set in the past.
Decide between direct send and queue worker Queue vs Direct Send Use direct send for time-sensitive mail (password resets, OTPs, payment confirmations) and the queue plugin for high-volume or webhook-triggered mail; beware open issues
Receive Mailgun webhooks for delivery events Webhook Handling Build a custom controller at a no-cache, no-CSRF route to receive Mailgun webhook POST requests; always verify the HMAC signature and reject events older than 5 minutes to prevent replay attacks — the drupal/mailgun module does not ship a webhook receiver as of 2.1.0.
Handle bounces, complaints, and unsubscribes Bounce & Complaint Handling Immediately suppress hard bounces and complaints via hook_mail_alter() checking a field_mail_status field on User; reconcile nightly with Mailgun's /v3//bounces API because webhooks can be missed.
Test and verify the setup end-to-end Verification & Testing Always run a direct curl test first to isolate Mailgun credential and DNS issues from Drupal, then confirm with drush eval through the full mail stack; API success ("Queued. Thank you.") does not mean delivered — check Mailgun Logs for actual delivery status.
Diagnose common errors (401, 421, 404, region mismatch) Common Errors Most failures fall into four categories — region mismatch (401), DNS not propagated (421), wrong working_domain (404), and sandbox recipient restriction; start with a direct curl test to isolate the layer before debugging Drupal.
Compare Mailgun against SendGrid, Postmark, Brevo, raw SMTP Alternatives Comparison Mailgun is the default developer-first choice for Drupal transactional email with EU region support and DKIM auto-rotation; choose Postmark for maximum deliverability on pure transactional, Brevo for budget/marketing-heavy sites or no-CC-required free tier, and SES only if already in the AWS ecosystem at scale.
Estimate cost and pick a plan tier Pricing & Tier Selection Go live on Free only with a CC on file (removes authorized-recipients restriction); upgrade to Basic ($15/10k) for volume or support needs, Foundation ($35/50k) when monthly sends exceed ~18k; skip dedicated IP until 100k+/month.