All posts
Drupal email that works: replace PHP mail() with proper SMTP
Gabe 3 min read

Drupal email that works: replace PHP mail() with proper SMTP

Drupal uses PHP's mail() function by default — the same broken path that sinks WordPress email in spam. This tutorial shows how to install the SMTP Authentication module, configure it for MailKite's SMTP relay, and route all Drupal site email through your DKIM-signed domain. Password resets, contact forms, content notifications — all deliverable.

Drupal sends email through PHP’s mail() function. The same function that makes WordPress email undeliverable makes Drupal email undeliverable. No DKIM, no SPF alignment, shared IP reputation. Password resets go to spam. Contact form submissions vanish. Content notification emails never arrive.

The fix is the same too: point Drupal at a proper SMTP relay. MailKite gives you one — smtp.mailkite.dev — with DKIM signing, SPF alignment, and delivery logs. Drupal’s SMTP Authentication module routes all site email through it.

Here’s the installation, configuration, and what to watch out for.

Why Drupal’s default email fails

Drupal calls drupal_mail(), which calls PHP’s mail(). PHP hands the message to whatever MTA your server runs. Three things go wrong:

  1. No DKIM. The message has no cryptographic signature. Gmail, Yahoo, and Outlook all check DKIM — no signature means a failing score.

  2. No SPF. The sending IP is your hosting provider’s server. Your domain’s SPF record doesn’t include it. Hard fail.

  3. Shared reputation. Your server shares an IP with hundreds of other sites. One spammer in the pool affects your deliverability.

For a Drupal site that sends transactional email — user registration, password resets, content notifications, contact forms — this is unacceptable. The emails are important and time-sensitive.

The fix: SMTP Authentication module

1. Install the module

composer require drupal/smtp
drush en smtp -y

The SMTP Authentication module overrides Drupal’s drupal_mail() to route through your configured SMTP server.

2. Verify your domain in MailKite

Add your domain to the MailKite dashboard. Publish the DNS records (MX, SPF, DKIM, DMARC). Under 5 minutes.

3. Configure the module

In Drupal admin, go to Configuration → System → SMTP Authentication:

SettingValue
Turn this module onYes
SMTP serversmtp.mailkite.dev
SMTP port587
Use encrypted protocolYes (STARTTLS)
SMTP usernamemailkite
SMTP passwordYour API key (mk_live_…)
From addressnotifications@yourdomain.com
From nameYour site name

Or configure via settings.php for code-managed deployment:

$config['smtp.settings']['smtp_on'] = TRUE;
$config['smtp.settings']['smtp_host'] = 'smtp.mailkite.dev';
$config['smtp.settings']['smtp_port'] = '587';
$config['smtp.settings']['smtp_protocol'] = 'tls';
$config['smtp.settings']['smtp_username'] = 'mailkite';
$config['smtp.settings']['smtp_password'] = 'mk_live_...';
$config['smtp.settings']['smtp_from'] = 'notifications@yourdomain.com';
$config['smtp.settings']['smtp_fromname'] = 'Your Drupal Site';

4. Verify

drush eval 'mail("you@yourdomain.com", "Test from MailKite", "It works!");'

Check the headers. DKIM, SPF, and DMARC should all pass.

What gets sent through MailKite

The SMTP module intercepts every drupal_mail() call:

  • User registration — account creation and verification emails
  • Password resets — “reset your password” links
  • Content notifications — comment alerts, content updates
  • Contact form submissions — messages from Drupal’s contact form
  • Module-generated email — any module that calls drupal_mail()

All routed through MailKite. All DKIM-signed. All logged.

Configuration management

If your team manages configuration through code (recommended for production Drupal), export the SMTP settings:

drush config-export -y

The smtp.settings config is included in the export. When deploying to a new environment, the settings deploy with the rest of your configuration. No manual UI configuration needed.

Receiving inbound email

For inbound email — creating content from email, powering a support inbox — create a custom module with a webhook controller:

// web/modules/custom/mailkite_inbound/mailkite_inbound.routing.yml
mailkite_inbound.receive:
  path: '/webhooks/mailkite'
  defaults:
    _controller: '\Drupal\mailkite_inbound\Controller\InboundController::handle'
  requirements:
    _access: 'TRUE'
  methods: [POST]
// web/modules/custom/mailkite_inbound/src/Controller/InboundController.php
namespace Drupal\mailkite_inbound\Controller;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class InboundController {
  public function handle(Request $request) {
    $payload = json_decode($request->getContent(), TRUE);

    // Verify signature
    $sig      = $request->server->get('HTTP_X_MAILKITE_SIGNATURE');
    $secret   = \Drupal::config('mailkite_inbound.settings')->get('webhook_secret');
    $computed = hash_hmac('sha256', json_encode($payload), $secret);

    if (!hash_equals($computed, $sig)) {
      return new JsonResponse(['error' => 'invalid signature'], 401);
    }

    // Process: create a node, log, or trigger a reaction
    \Drupal::logger('mailkite_inbound')->notice(
      'Inbound email from @from: @subject',
      ['@from' => $payload['from'], '@subject' => $payload['subject']]
    );

    return new JsonResponse(['ok' => TRUE]);
  }
}

Register the route and set your domain’s webhook to https://yoursite.com/webhooks/mailkite.

How it compares

PHP mail() defaultSMTP module + MailKite
DKIM signingNoneAutomatic, per-domain
SPF alignmentFails (shared server)Passes (your domain)
Inbox placement~40–60%~95%+
Delivery logsServer mail.log onlyDashboard + API
ConfigurationServer-level MTADrupal admin or settings.php
Inbound emailNot supportedWebhook → custom module
Config managementN/AExports with drush config-export

FAQ

Will this affect Drupal’s HTML email templates? No. The SMTP module only changes how email is sent, not what Drupal puts in it. Templates, content, and formatting stay the same.

What about Drupal Commerce order emails? Drupal Commerce uses drupal_mail() for order notifications. The SMTP module catches them automatically — same as WordPress’s WooCommerce emails.

Can I use this with multiple environments? Yes. Set smtp_on = FALSE in development (emails go to php://log or your dev SMTP server) and smtp_on = TRUE in production. The settings.php approach makes this trivial.

What about the SMTP Authentication module vs. Mail system module? The SMTP Authentication module is simpler and focused on one job: route drupal_mail() through SMTP. The Mail system module is more flexible but requires more configuration. For most Drupal sites, the SMTP Authentication module is the right choice.


Drupal’s mail() is broken for production. Fix it with four DNS records and one module. Start free — unlimited domains, no credit card.

Related: Fix WordPress email deliverability — the same fix for WordPress · Laravel email configuration — the PHP framework version · Set up email once, reuse every project — the architecture

Discuss this post: Hacker News Share on X Share on LinkedIn

Related posts