Get your API key
All integrations WordPress

WordPress + MailKite

WordPress sends mail through PHP mail() by default — unauthenticated, often blocked, and invisible when it fails. Point it at MailKite and every notification, password reset, and form submission goes out over your own DKIM-signed domain, with a log that tells you what happened.

What you need

  • A verified domain with SPF + DKIM published
  • Your API key (mk_live_…) — the plugin can create an account for you if you don't have one yet

Our own plugin — free and open source. Sending, a searchable email log, failover, and inbound in one place. Download the zip, then Plugins → Add New → Upload Plugin. WordPress 6.2+, PHP 8.1+.

download
https://github.com/mailkite/mailkite-smtp/releases/latest/download/mailkite-smtp.zip

The MailKite SMTP plugin

Free, open source (GPL-2.0), and no paid tier gating the useful parts:

  • Send through MailKite, any SMTP host, or a provider API — SendGrid, Brevo and Mailgun are built in, bring your own key.
  • A real email log. Every message, its status, and the error when there was one. Most plugins charge for this.
  • Automatic failover. If an API send fails, the next mailer takes it — and the log says plainly that it fell back, rather than reporting a success you didn't get.
  • Instant failure alerts by email, Slack, Discord, or webhook.
  • Inbound email. Receive mail at your domain, read it in wp-admin, and reply in-thread. The webhook is registered on your MailKite account automatically.
  • Domain health. SPF and DMARC checked on a schedule, with an alert when a record drifts.

All releases and changelogs →

Test it

The plugin has a Send Test tab that reports the mailer used and the error verbatim when one comes back. Or from WP-CLI:

terminal
wp eval 'wp_mail("you@yourdomain.com", "Test from MailKite", "It works!");'

Give your users real mailboxes

MailKite Mailboxes is a companion plugin: it gives the people who use your site a real address on your domain — jane@yoursite.com — that works in Apple Mail, Thunderbird, or any IMAP client, plus an Inbox screen in wp-admin and a [mailkite_inbox] shortcode for the front end. New mail appears without a page reload.

Everything is off until you switch it on, and addresses can be reserved so nobody claims admin@. It needs MailKite SMTP installed first — that's where the account connection and domain live.

Download MailKite Mailboxes · releases

Act on inbound email

With the plugin installed, inbound mail is already received, verified, and logged. To do something with it — open a ticket, create a post, notify a team — hook the action it fires:

functions.php
// functions.php — runs for every inbound message the plugin receives.
// The plugin has already verified the signature and logged the mail.
add_action('mailkite_smtp_inbound', function (array $message) {
// $message is the event payload: id, threadId, subject, text, html, attachments,
// and from/to as objects — $message['from']['address'], not a plain string.
$subject = $message['subject'] ?? '';
$sender = $message['from']['address'] ?? '';

if (str_contains($subject, '[support]')) {
wp_insert_post([
'post_type' => 'ticket',
'post_title' => $subject,
'post_content' => $message['text'] ?? '',
'post_status' => 'publish',
'meta_input' => [
'from' => $sender,
'thread_id' => $message['threadId'] ?? $message['id'] ?? '',
],
]);
}
});

Not running the plugin? Verify the signature yourself. The header carries two fields and the HMAC covers "<t>.<raw body>" — sign the raw bytes, because re-encoding the decoded payload changes whitespace and key order and will never match:

webhook.php
<?php
// Inbound webhook, verified by hand. See /docs/webhook-security.

$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_MAILKITE_SIGNATURE'] ?? ''; // t=1750000000000,v1=4f1a9c…
$secret = getenv('MK_WEBHOOK_SECRET'); // whsec_… for this route

$parts = [];
foreach (explode(',', $header) as $pair) {
[$k, $v] = array_pad(explode('=', $pair, 2), 2, '');
$parts[trim($k)] = trim($v);
}
$t = $parts['t'] ?? '';
$sig = $parts['v1'] ?? '';

// Sign the RAW bytes: json_decode + json_encode changes whitespace and key order,
// and the signature will never match again.
$computed = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!$t || !hash_equals($computed, $sig)) {
http_response_code(401);
exit;
}

// Reject replays: t is milliseconds, and moves on every delivery attempt.
if (abs(time() - (int) $t / 1000) > 300) {
http_response_code(401);
exit;
}

$payload = json_decode($raw, true);
error_log("MailKite inbound: {$payload['from']['address']} — {$payload['subject']}");

http_response_code(200);
echo '{"ok":true}';

See Inbound webhooks for the payload and signature verification for the full scheme.

Troubleshooting

  • Emails not sending — confirm the From address is on a verified domain. The plugin's Email Log records the reason the provider gave.
  • 535 Authentication failed — over SMTP the password is your mk_live_… API key, not a separate SMTP password.
  • Mail sent but never arrives — check the log for “sent via fallback”. That means the primary mailer refused it and a backup took over, which is worth fixing even though the mail went out.
  • Delayed delivery — WP-Cron only runs when someone visits the site, so a quiet site sends late. Set define('DISABLE_WP_CRON', true); and have your host call wp-cron.php on a real schedule.
  • Two plugins fighting over wp_mail() — only one can win. Deactivate the other SMTP plugin rather than configuring both.

See the SMTP relay docs for the full connection reference, or all integrations for other platforms.