Symfony Mailer + MailKite
Symfony Mailer resolves transports from a DSN string. This package makes
mailkite+api:// a valid one — install it, set a single env var,
and every MailerInterface::send() in a plain Symfony app, or in
anything else built on Symfony Mailer, delivers through
MailKite's Send API. No SMTP round-trip.
What you need
- A verified domain with SPF + DKIM published
- Your API key (
mk_live_…) - PHP 8.1+ and Symfony Mailer 6.4 or 7.x
Install
composer require mailkite/symfony-mailer Configure
Set your Mailer DSN — the API key goes in the DSN's user slot:
# .env / MAILER_DSN
MAILER_DSN=mailkite+api://mk_live_...@default
In a standard Symfony app with symfony/framework-bundle, that's it —
Symfony's mailer autoconfigures any installed transport factory. The from
domain must be verified (SPF + DKIM) on your MailKite account.
No framework DI?
Build the transport directly:
use MailKite\Mailer\Transport\MailKiteTransportFactory;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport\Dsn;
$transport = (new MailKiteTransportFactory())->create(
Dsn::fromString('mailkite+api://mk_live_...@default')
);
$mailer = new Mailer($transport); Send email
use Symfony\Component\Mime\Email;
$email = (new Email())
->from('hello@yourdomain.com')
->to('ada@example.com')
->subject('Your invoice #1042')
->html('<p>Thanks!</p>')
->text('Thanks!');
$mailer->send($email); Mautic
Mautic 5+ runs on Symfony Mailer with a DSN field in Configuration → Email Settings, and it consumes any transport installed via Composer — install this package alongside Mautic, then set:
# Mautic: Configuration → Email Settings
mailkite+api://mk_live_...@default Full walkthrough (SMTP fallback for managed hosts without shell access): mailkite.dev/docs/integrations/mautic.
What maps where
| Symfony Email | MailKite send | Notes |
|---|---|---|
| from, to, cc, bcc | from, to, cc, bcc | display names preserved |
| subject | subject | omitted when null |
| HTML + text bodies | html, text | resource bodies read to string |
| reply-to (single) | replyTo | 2+ addresses throws — API takes one |
| In-Reply-To header | inReplyTo | threading |
| attachments | attachments[] | base64 content + filename + contentType |
Honest failures, not silent drops
The MailKite send API carries a fixed envelope, so the transport throws a TransportException (with the API's own error message) instead of pretending:
- API errors (unverified domain, suppressed recipient, rate limit, …) surface verbatim with their HTTP status.
- Custom headers the API can't deliver are refused, not dropped.
- Inline (
cid:-embedded) images are refused — host the image at a URL instead. - Multiple reply-to addresses are refused — the API takes one.
Batch sending
Symfony-provided transports send one email per request, and this one does too —
doSend() is per-message by contract. For high-volume sends, call the
PHP SDK's sendBatch() directly rather than looping the transport.
Laravel
Using Laravel? Install mailkite/laravel instead —
it wraps this same transport with MAIL_MAILER=mailkite and Laravel's
config conventions.
Troubleshooting
- DSN not picked up — clear the app cache after changing
MAILER_DSN(bin/console cache:clearin Symfony, or Mautic's equivalent). - "User is not set" (IncompleteDsnException) — the API key must be in the DSN's user slot:
mailkite+api://mk_live_xxx@default, not justmailkite+api://default. - Custom-header exception — the API can't carry arbitrary SMTP-style headers; remove tag/metadata headers before sending, or move that data into your own logging.
See the SMTP relay docs for the plain-SMTP path, or all integrations for other platforms.