> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mailchannels.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Symfony

> Send email through Symfony Mailer using the MailChannels transport plugin.

The `mailchannels/mailchannels-php` package ships a [Symfony Mailer](https://symfony.com/doc/current/mailer.html) transport.
If you already have a Symfony application that builds `Symfony\Component\Mime\Email` objects and sends them through
`MailerInterface`, you can switch to MailChannels with just a few lines of configuration.

<Info>
  This plugin is optional and lives alongside the rest of the PHP SDK. The plugin itself can only send mail.
  It doesn't implement the full API surface (sub-accounts, keys, webhooks, etc.). For those features, use the SDK's `Client` directly.
</Info>

## Prerequisites

Before you send your first message, follow the [PHP quickstart](/email-api/php/quickstart#prerequisites) prerequisite section.
This will walk you through creating an account, generating an API key, and adding the required Domain
Lockdown and SPF DNS records.

## Installation

Install the SDK and the Symfony Mailer component:

```bash theme={null}
composer require mailchannels/mailchannels-php symfony/mailer
```

The SDK also needs a PSR-18 HTTP client and PSR-17 factories. Symfony HTTP Client + Nyholm PSR-7 is a natural fit for a
Symfony project (Guzzle works too, but install one or the other, not both):

```bash theme={null}
composer require symfony/http-client nyholm/psr7
```

### Configuring Symfony

<Steps>
  <Step title="Register the transport factory">
    Symfony discovers mailer transports through tagged services. Register the factory in `config/services.yaml`:

    ```yaml config/services.yaml theme={null}
    services:
        MailChannels\Plugins\Symfony\Transport\MailChannelsTransportFactory:
            tags: ['mailer.transport_factory']
    ```

    See Symfony's docs on [custom transport factories](https://symfony.com/doc/current/mailer.html#custom-transport-factories)
    for background on how this mechanism works.
  </Step>

  <Step title="Point MAILER_DSN at MailChannels">
    Set the `MAILER_DSN` environment variable, in your `.env` file or your environment:

    ```bash .env theme={null}
    MAILER_DSN=mailchannels+api://${MAILCHANNELS_API_KEY}@default
    ```

    * Scheme: `mailchannels` or `mailchannels+api` (both accepted).
    * DSN user: your MailChannels API key.
    * DSN host: leave as `default` for the production API endpoint.

    See Symfony's [transport setup](https://symfony.com/doc/current/mailer.html#transport-setup) docs for more on the DSN
    format and how it's typically wired up per-environment.
  </Step>
</Steps>

## Sending mail

Once configured, send mail through the standard `MailerInterface`. No MailChannels-specific code is required for a
basic send:

```php theme={null}
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class YourService
{
    public function __construct(private readonly MailerInterface $mailer) {}

    public function sendWelcomeEmail(): void
    {
        $email = (new Email())
            ->from('sender@example.com')
            ->to('recipient@example.com')
            ->subject('Welcome to MailChannels')
            ->text('Plain-text body.')
            ->html('<p>HTML body.</p>');

        $this->mailer->send($email);
    }
}
```

See Symfony's docs on [creating and sending messages](https://symfony.com/doc/current/mailer.html#creating-sending-messages)
for everything else `Email` supports (multiple recipients, reply-to, attachments, and so on).

<Note>
  This transport only builds one personalization per message. If you need to send multiple individualized emails in one
  request, call the SDK's `Client` directly instead of going through `MailerInterface`. See
  [How emails are structured](/email-api/how-emails-are-structured) for more information on personalizations.
</Note>

### Control headers

Symfony's `Email` object has no equivalent for some MailChannels API features. Reach those instead through custom
headers prefixed with `x-mailchannels-`, set on the `Email`'s header bag. The `x-mailchannels-*` headers are stripped
before the message is sent, so recipients never see them.

```php theme={null}
$email->getHeaders()->addTextHeader('x-mailchannels-transactional', 'true');
```

<Note>
  If the same custom header is added twice, only the **last** value is used.
</Note>

Boolean headers accept only the literal strings `"true"` / `"false"`; anything else throws
`Symfony\Component\Mailer\Exception\TransportException`. An empty value is treated as unset (falls back to the default).

| Header                                 | Description                                                                                                                                                                                                                                         |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-mailchannels-send-async`            | Queue the message (`true`, the default) instead of sending it synchronously (`false`). Equivalent to calling the SDK's `queue()` vs. `send()`. Queued sends report delivery status via [webhooks](/email-api/webhooks) rather than in the response. |
| `x-mailchannels-click-tracking-enable` | Enable click tracking.                                                                                                                                                                                                                              |
| `x-mailchannels-click-tracking-domain` | Custom domain for click-tracking links. See [Custom tracking](/email-api/custom-tracking).                                                                                                                                                          |
| `x-mailchannels-open-tracking-enable`  | Enable open tracking.                                                                                                                                                                                                                               |
| `x-mailchannels-open-tracking-domain`  | Custom domain for open-tracking links. See [Custom tracking](/email-api/custom-tracking).                                                                                                                                                           |
| `x-mailchannels-unsubscribe-domain`    | Custom domain for unsubscribe links. See [Custom tracking](/email-api/custom-tracking).                                                                                                                                                             |
| `x-mailchannels-transactional`         | Whether the mail is transactional or non-transactional (marketing, etc.). Affects List-Unsubscribe handling, see [Unsubscribe](/email-api/unsubscribe).                                                                                             |
| `x-mailchannels-dkim-domain`           | Domain to use for DKIM signing.                                                                                                                                                                                                                     |
| `x-mailchannels-dkim-selector`         | Selector to use for DKIM signing.                                                                                                                                                                                                                   |
| `x-mailchannels-dkim-private-key`      | Private key to use for DKIM signing.                                                                                                                                                                                                                |
| `x-mailchannels-campaign-id`           | Campaign that this message is part of.                                                                                                                                                                                                              |

Each of these corresponds directly to a field on the [send endpoint](/api-reference/send/send-an-email). See that
reference for full semantics.

<Warning>
  This integration only supports DKIM via the `x-mailchannels-dkim-*` headers above.

  If a `DKIM-Signature` header is already present on the message, the message is rejected, since the provided signature
  won't match the message the API constructs.

  See [DKIM signing and key management](/email-api/configuring-dkim)
  for information on how MailChannels handles signing and keys.
</Warning>

## Reading the result

* Synchronous send (`x-mailchannels-send-async: false`): the sent message's ID is written back onto Symfony's
  `SentMessage` object, so you can read it after sending.
* Queued (default): the API doesn't return a message ID per call, so Symfony's own generated message ID is left
  untouched on `SentMessage`.

```php theme={null}
$sentMessage = $this->mailer->send($email);
echo $sentMessage?->getMessageId();
```

## Error handling

Any SDK exception raised while sending (authentication, validation, rate limiting, server errors) is caught and
re-thrown as `Symfony\Component\Mailer\Exception\TransportException`.

Access the original exception via `$e->getPrevious()` to inspect it:

```php theme={null}
use Symfony\Component\Mailer\Exception\TransportException;

try {
    $this->mailer->send($email);
} catch (TransportException $e) {
    $previous = $e->getPrevious();
    if ($previous instanceof \MailChannels\Exception\RateLimitException) {
        // handle rate limit
    } elseif ($previous instanceof \MailChannels\Exception\ValidationException) {
        // handle validation error
    } else {
        // handle other transport errors
    }
}
```

## Limitations

* **One personalization per message.** No per-recipient template data or per-recipient header overrides through this
  transport.
* **No Mustache templating.** This transport has no way to use mailchannels' mustache template rendering. If you need
  to do so, use Symfony's [Template Support](https://symfony.com/doc/current/templates.html), or use the SDK's `Client`
  directly; see [Templates](/email-api/templates) for how.
* **DKIM only via the MailChannels API**, as described above.

## Webhooks

The plugin also has optional support for parsing webhook events into Symfony's `RemoteEvent` objects.

### Installation

Install the Symfony Webhook and RemoteEvent components, as well as ext-sodium for webhook signature verification:

```bash theme={null}
composer require symfony/webhook symfony/remote-event ext-sodium
```

<Note>
  While the Symfony mailer plugin requires version 6.4 or higher, the webhook plugin requires Symfony 7.2 or higher,
  as previous versions do not have support for batched webhook processing.
</Note>

### Configuring Symfony

<Steps>
  <Step title="Register the webhook service">
    Register the webhook parser in `config/services.yaml`:

    ```yaml config/services.yaml theme={null}
    services:
        MailChannels\Plugins\Symfony\Webhook\MailChannelsRequestParser: ~
    ```
  </Step>

  <Step title="Add the webhook route">
    Register the webhook route in `config/webhooks.yaml`:

    ```yaml config/webhooks.yaml theme={null}
    framework:
      webhook:
        routing:
          mailchannels:
            service: MailChannels\Plugins\Symfony\Webhook\MailChannelsRequestParser
    ```

    See the Symfony docs on [webhook endpoints](https://symfony.com/doc/current/webhook.html#a-centralized-webhook-endpoint) for more information on how this works.
  </Step>
</Steps>

You will now have a webhook route available at `/webhook/mailchannels`.

### Consuming RemoteEvents

When a webhook is received, it will automatically be parsed into a `Symfony\Component\RemoteEvent\RemoteEvent` object and passed to your consumer.

```php src/RemoteEvent/MailChannelsWebhookConsumer.php theme={null}
namespace App\RemoteEvent;

use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
use Symfony\Component\RemoteEvent\RemoteEvent;

#[AsRemoteEventConsumer('mailchannels')]  // must match routing name
final class MailChannelsWebhookConsumer implements ConsumerInterface
{
    public function consume(RemoteEvent $event): void
    {
        // handle the event based on your business logic
    }
}
```

Almost all events will automatically be parsed into Symfony's `MailerDeliveryEvent` or `MailerEngagementEvent`,
with additional information about the type of event and the message it relates to.
See the [Symfony RemoteEvent docs](https://symfony.com/doc/current/webhook.html#consuming-the-remoteevent) for more information on how to consume events.

The MailChannels webhook integration adds one extra RemoteEvent type:  `MailChannels\Plugins\Symfony\RemoteEvent\TestEvent`.

This will be used when you send a test webhook from the MailChannels dashboard or API.
You can use this to verify that your webhook endpoint is working correctly.

## Next steps

* Read the [webhooks guide](/email-api/webhooks) to learn more about real-time notifications for email delivery,
  bounces, and complaints.
* Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API.
