User Docs Developer Docs | |

Email Provider

New in 2.3.6 — Plugins can take over outbound email delivery for the entire site, routing all system emails through an external service (Mailchimp Transactional, SendGrid, Postmark, etc.) instead of the built-in PHP mailer.

Declaring the Capability

In plugin_info.json, declare that your plugin can handle email:

"capabilities": {
    "email": ["core", "self"]
}
Value Meaning
"self" Plugin sends its own emails only (order confirmations, notifications, etc.)
"core" Plugin can also handle all core system emails (contact forms, password resets, Shield activation, etc.)

Use ["core", "self"] if your plugin also sends its own transactional emails. Use ["core"] if it only acts as a transport layer.

Implementing EmailProviderInterface

Your plugin (or a dedicated service class) must implement App\Interfaces\EmailProviderInterface:

<?php

namespace Plugins\MailchimpMailer;

use App\Interfaces\EmailProviderInterface;

class MailchimpProvider implements EmailProviderInterface
{
    public function handleEmail(array $data): bool
    {
        // $data keys:
        //   to          string[]      Recipient email addresses
        //   from        string        Sender email address
        //   fromName    string        Sender display name
        //   subject     string        Email subject
        //   body        string        HTML or plain text body
        //   altMessage  string        Plain text alt body (for HTML emails)
        //   cc          string[]      CC addresses
        //   bcc         string[]      BCC addresses
        //   replyTo     string|null   Reply-to address
        //   replyToName string|null   Reply-to display name
        //   attachments array         CI4 native attachment structure

        $apiKey = setting('MailchimpMailer.apiKey');
        if (empty($apiKey)) {
            // Not configured — fall through to core mailer
            return false;
        }

        try {
            $this->sendViaMandrill($apiKey, $data);
            return true;  // Handled — core mailer is skipped
        } catch (\Throwable $e) {
            log_message('error', 'MailchimpMailer: send failed — ' . $e->getMessage());
            return false; // Fall through to core as a safety net
        }
    }
}

Return values:

  • true — your plugin handled delivery. The core PHP/SMTP mailer is skipped.
  • false — your plugin did not handle it (unconfigured, or error). Core mailer runs as normal.

Always return false gracefully when unconfigured so that email still works on a fresh install before the plugin is configured.

Registering in register()

Call service('emailProvider')->register() from your plugin's register() method:

public function register(): void
{
    service('emailProvider')->register($this->getSlug(), new MailchimpProvider());
}

EmailProviderService is a shared CI4 service. register() is called once per request at boot time, before any controller runs.

Admin → Settings → Email

When your plugin is active and registered, it appears in the Email Provider dropdown at Admin → Settings → Email. The admin can switch between Core (default) and your plugin at any time without deactivating it.

Activation Modal

When your plugin is the first plugin declaring "core" email capability to be activated, the admin sees a prompt:

"This plugin can handle core system emails (contact forms, password resets, etc.) as well as its own. Do you want it to take over core email delivery?"

Subsequent "core"-capable plugins do not show the modal again — the existing provider selection is preserved.

Checklist

  • [ ] capabilities.email declared in plugin_info.json
  • [ ] EmailProviderInterface implemented
  • [ ] Provider registered in register()
  • [ ] handleEmail() returns false gracefully when unconfigured