# Speed Up Email Sending: MailBaby API Transport + Queue

## Context

Email sending via `mautic:broadcasts:send` cron is very slow after switching from AWS SES API to MailBaby SMTP (`relay.mailbaby.net:25`). The root causes:

1. **SMTP is slow** — each email requires a full TCP + SMTP handshake (EHLO → AUTH → MAIL FROM → RCPT TO → DATA → QUIT)
2. **Synchronous messenger** — `messenger_dsn_email = sync://` means each email blocks until SMTP completes
3. **Single-threaded cron** — current cron runs without `--batch`, `--thread-id`, or `--max-threads`

## Solution: Two-Part Fix

### Part 1: Custom MailBaby API Transport (Code Change)

Build a Symfony Mailer transport that sends via MailBaby's REST API (`POST https://api.mailbaby.net/mail/advsend`) instead of SMTP. HTTP API calls are significantly faster than SMTP — no multi-step protocol handshake, uses keep-alive connections, and enables potential future batching.

**Files to create:**

#### 1. `docroot/app/bundles/EmailBundle/Mailer/Transport/MailBabyApiTransport.php`

Extends `Symfony\Component\Mailer\Transport\AbstractApiTransport`. Implements `doSendApi()`:
- Converts Symfony `Email` object to MailBaby API format (from, to, cc, bcc, subject, body, attachments)
- POSTs JSON to `https://api.mailbaby.net/mail/advsend`
- Sets `X-API-KEY` header for authentication
- Handles HTML body (the API auto-detects content type)
- Parses response and throws `HttpTransportException` on failure

#### 2. `docroot/app/bundles/EmailBundle/Mailer/Transport/MailBabyTransportFactory.php`

Extends `Symfony\Component\Mailer\Transport\AbstractTransportFactory`. Supports DSN scheme `mailbaby+api`:
- `getSupportedSchemes()` returns `['mailbaby+api', 'mailbaby']`
- `create(Dsn $dsn)` extracts API key from DSN user field, creates `MailBabyApiTransport`

#### 3. Register the factory in `docroot/app/bundles/EmailBundle/Config/services.php`

Add the transport factory service tagged with `mailer.transport_factory`:
```php
$services->set(MailBabyTransportFactory::class)
    ->tag('mailer.transport_factory');
```

#### 4. Update `config/local.php` — mailer DSN

Change:
```php
'mailer_dsn' => 'smtp://mb75420:kJAjpB27Uerer3AtZhVS@relay.mailbaby.net:25',
```
To:
```php
'mailer_dsn' => 'mailbaby+api://MAILBABY_API_KEY@api.mailbaby.net',
```
(User needs to get their API key from https://my.interserver.net → Settings → Account Security)

### Part 2: Async Queue + Parallel Cron (Config Change)

#### 5. Update `config/local.php` — messenger DSN

Change:
```php
'messenger_dsn_email' => 'sync://',
```
To:
```php
'messenger_dsn_email' => 'doctrine://default',
```

This queues emails to the database. The `messenger_messages` table will be **auto-created** by Symfony on first use (`auto_setup: true` is the default in `vendor/symfony/doctrine-messenger/Transport/Connection.php:50`). The `mautic:broadcasts:send` command will queue all emails nearly instantly, and a separate consumer cron processes them in the background.

#### 6. Add messenger consumer cron job

```
* * * * * /usr/local/bin/ea-php82 /home/whizzmailer/public_html/swiss-belhotel-v2/bin/console messenger:consume email --limit=50 --time-limit=55 --env=prod --no-interaction >> /home/whizzmailer/public_html/swiss-belhotel-v2/var/logs/messenger.log 2>&1
```

For higher throughput, add a second parallel consumer:
```
* * * * * /usr/local/bin/ea-php82 /home/whizzmailer/public_html/swiss-belhotel-v2/bin/console messenger:consume email --limit=50 --time-limit=55 --env=prod --no-interaction >> /home/whizzmailer/public_html/swiss-belhotel-v2/var/logs/messenger2.log 2>&1
```

Two consumers = ~100 API calls/minute in parallel.

#### 7. Update broadcasts cron with batch flag

Change current cron from:
```
/usr/local/bin/ea-php82 .../bin/console mautic:broadcasts:send
```
To:
```
/usr/local/bin/ea-php82 .../bin/console mautic:broadcasts:send --batch=100
```

The `--batch` flag enables batched processing with progress output and prevents loading all contacts into memory at once.

### Part 3: Cache Clear

```bash
/opt/cpanel/ea-php82/root/usr/bin/php /home/whizzmailer/public_html/swiss-belhotel-v2/bin/console cache:clear --env=prod
```

## Key Reference Files

| File | Purpose |
|------|---------|
| `vendor/symfony/mailer/Transport/AbstractApiTransport.php` | Base class for API transports |
| `vendor/symfony/mailer/Transport/AbstractTransportFactory.php` | Base class for transport factories |
| `docroot/app/bundles/EmailBundle/Mailer/Transport/TransportFactory.php` | Mautic's transport factory decorator |
| `docroot/app/bundles/EmailBundle/Config/services.php` (line 31) | Service registration for transport factory |
| `docroot/app/bundles/EmailBundle/Model/SendEmailToContact.php` | Where `$this->mailer->send()` is called (line 394 of MailHelper) |
| `docroot/app/bundles/EmailBundle/Helper/MailHelper.php` (line 394) | Calls `$this->mailer->send($this->message)` via Symfony Mailer |
| `config/local.php` (lines 678, 771) | mailer_dsn and messenger_dsn_email |

## MailBaby API Reference

- **Endpoint**: `POST https://api.mailbaby.net/mail/advsend`
- **Auth**: `X-API-KEY` header
- **Body**: JSON with `from`, `to`, `subject`, `body`, `cc`, `bcc`, `replyto`, `attachments`
- **Address format**: `{"name": "Name", "email": "addr@example.com"}` or plain string

## Switching Back to AWS SES (Future)

When AWS SES account is unpaused, just change one line in `config/local.php`:
```php
'mailer_dsn' => 'ses+api://ACCESS_KEY:SECRET_KEY@default?region=REGION',
```
`symfony/amazon-mailer` is already installed. The queue/consumer setup works with any transport — no other changes needed.

## Verification

1. After creating transport files + cache clear, test with a single email from Mautic UI
2. Check Mautic logs (`var/logs/`) for successful API response
3. With doctrine queue enabled, verify emails appear in `messenger_messages` table
4. Run `messenger:consume email --limit=1 -vv` manually to see processing
5. Confirm test email is received
6. Enable cron jobs and send a small test broadcast to verify end-to-end flow
