# Dual-Transport Setup: SES + MailBaby with Bounce Handling

## Context

You use AWS SES (`ses+api://`) as your email transport with 3-layer bounce handling (SQS poller, AmazonSESBundle webhook, IMAP). When SES bounce rates spike, you want to temporarily switch to MailBaby (SMTP relay by InterServer), send some emails to let SES reputation recover, then switch back.

**Problem:** MailBaby has **no webhook/push notification** for bounces. The only way to detect bounces is polling their API:
- `GET /mail/blocks` — returns blocked/bounced addresses (3 arrays: `local[]`, `mbtrap[]`, `subject[]`)
- `GET /mail/log` — mail log with delivery response info

**Solution:** Build a MailBaby bounce polling script (modeled on your existing SQS poller) that runs via cron alongside the SQS poller. Both pollers are harmless when their transport isn't active.

---

## Implementation Plan

### Step 1: Create `docroot/scripts/mailbaby_config.php`

Config file mirroring `sqs_config.php` structure:
```
mailbaby:
  api_key: (stored securely)
  api_base_url: https://api.mail.baby
mautic:
  (same block as sqs_config.php — reuse credentials)
logging:
  log_file: var/logs/mailbaby_bounces.log
  log_level: INFO
processing:
  dry_run: false
  state_file: docroot/scripts/.mailbaby_last_poll.json
```

### Step 2: Create `docroot/scripts/process_mailbaby_bounces.php`

Reuse the same architecture as `process_sqs_bounces.php`:
- **Same** `extractEmail()` helper, `Logger` class, `MauticApiClient` class (copy from SQS script)
- **Same** file-lock pattern (`.process_mailbaby_bounces.lock`)
- **Same** stats tracking and summary output

**Processing logic:**
1. Call `GET /mail/blocks` with header `X-API-KEY`
2. Iterate `local[]` + `mbtrap[]` arrays — each entry has `to` (recipient), `date`, `from`, `subject`, `messageId`
3. Load state file (`.mailbaby_last_poll.json`) containing previously processed email addresses
4. For each **new** blocked address not already in state:
   - Find Mautic contact via API
   - Add DNC (reason=2 BOUNCED, comment="MailBaby block: {from} → {to} at {date}")
   - Append to `var/logs/bounced_emails.txt`
5. Save updated state file with all processed addresses + timestamp
6. Call `POST /mail/blocks/delete` to clean processed blocks from MailBaby (prevents re-processing and keeps their list clean)

**State tracking** (`.mailbaby_last_poll.json`):
```json
{
  "last_poll": "2026-03-18T12:00:00Z",
  "processed_emails": ["bad@example.com", "invalid@test.com"]
}
```

### Step 3: Set up cron job

```
*/5 * * * * /opt/cpanel/ea-php82/root/usr/bin/php /home/whizzmailer/public_html/swiss-belhotel-v2/docroot/scripts/process_mailbaby_bounces.php >> /home/whizzmailer/public_html/swiss-belhotel-v2/var/logs/mailbaby_bounces_cron.log 2>&1
```

Runs every 5 min alongside the existing SQS poller. When MailBaby isn't in use, the blocks list will be empty and the script returns immediately.

### Step 4: Transport switching procedure

**To switch to MailBaby:**
1. Mautic Admin → Configuration → Email Settings
2. Change DSN to: `smtp://YOUR_MAILBABY_LOGIN:YOUR_MAILBABY_PASSWORD@relay.mailbaby.net:587`
3. Save. All new sends go through MailBaby.
4. SQS poller keeps running (processes any remaining SES bounces from emails already in flight)
5. MailBaby poller picks up new bounces from MailBaby sends

**To switch back to SES:**
1. Change DSN back to: `ses+api://ACCESS_KEY:SECRET@default?region=eu-west-1`
2. Save. SES resumes. AmazonSESBundle webhook reactivates automatically (it checks DSN scheme).

---

## Files to Create/Modify

| File | Action |
|---|---|
| `docroot/scripts/process_mailbaby_bounces.php` | **CREATE** — main polling script |
| `docroot/scripts/mailbaby_config.php` | **CREATE** — credentials & settings |
| crontab | **ADD** — cron entry for mailbaby poller |

No existing files need modification. Both pollers coexist independently.

---

## Key Design Decisions

1. **Reuse MauticApiClient class** — copy from SQS script (same Mautic API credentials, same DNC logic)
2. **State file prevents duplicate DNC** — track processed emails so re-polling the same blocks doesn't create duplicate entries
3. **Both pollers always run** — no coupling between transport choice and cron jobs; each poller is a no-op when its transport isn't active
4. **AmazonSESBundle auto-disables** — its `CallbackSubscriber` checks `dsn->getScheme() === 'ses+api'` and silently skips if not SES
5. **mails.so validation continues working** — it validates at send time regardless of transport, providing pre-send protection for both SES and MailBaby

---

## Limitations to Accept

- **No bounce type distinction** from MailBaby — all blocks treated as permanent bounces
- **No spam complaint detection** — MailBaby doesn't expose complaint data
- **No diagnostic codes** — comments will be generic ("MailBaby block") vs SES's detailed diagnostic info
- **Polling delay** — up to 5 min delay vs SES's near-real-time webhook
- **mails.so becomes critical** — pre-send validation is your primary defense against bad addresses when using MailBaby

---

## Verification

1. **Dry run first:** Set `dry_run: true` in config, run manually:
   ```
   /opt/cpanel/ea-php82/root/usr/bin/php docroot/scripts/process_mailbaby_bounces.php
   ```
2. **Check logs:** `cat var/logs/mailbaby_bounces.log`
3. **Test with MailBaby active:** Switch transport to MailBaby SMTP, send a test email to a known-bad address, wait 5 min, verify DNC was added
4. **Verify SQS poller unaffected:** Confirm `var/logs/sqs_bounces.log` still runs (shows "No messages in queue")
5. **Switch back to SES:** Change DSN back, confirm AmazonSESBundle webhook processes correctly
