# Test Infrastructure: SMTP Server (Mailpit)

## Overview

The test SMTP server uses [Mailpit](https://mailpit.axllent.org/), a lightweight email testing tool that captures all outgoing emails without delivering them. It provides both an SMTP server for sending and a web UI for inspecting captured messages.

## Image & Ports

- **Image**: `axllent/mailpit:latest`
- **SMTP port**: 1025
- **Web UI / API**: `http://localhost:8025`

## Configuration

| Setting | Value | Description |
|---------|-------|-------------|
| `MP_SMTP_AUTH_ACCEPT_ANY` | `1` | Accept any SMTP credentials (or none) — no real authentication |
| `MP_SMTP_AUTH_ALLOW_INSECURE` | `1` | Allow auth over plain SMTP (no TLS required) — dev only |
| `MP_MAX_MESSAGES` | `500` | Maximum stored messages before oldest are auto-deleted |

Mailpit accepts all emails regardless of sender/recipient domain. No emails leave the server — they are captured and viewable in the web UI.

## SMTP Connection Settings

For `appsettings.Development.json` (Notification Service):

```json
{
  "Smtp": {
    "Server": "localhost",
    "Port": 1025,
    "AuthMode": "Basic",
    "Credentials": "test:test",
    "TlsMode": "None",
    "FromAddress": "scada-notifications@company.com",
    "ConnectionTimeout": 30
  }
}
```

> **`Server` host**: use `localhost` only when the Notification Service runs directly on
> the host. When it runs inside the docker cluster, set `Server` to the container name
> `scadabridge-smtp` — the cluster compose stack and the infra compose stack share the
> `scadabridge-net` network, so the container is reachable by name.

The delivery service (`MailKitSmtpClientWrapper`) only accepts `Basic` or `OAuth2` —
there is no "no auth" mode — so the working config above uses `Basic`:
- **Basic Auth**: `MP_SMTP_AUTH_ACCEPT_ANY` makes Mailpit accept any `username:password`,
  so use a throwaway value such as `test:test`. This exercises the real auth code path
  without a real server.
- **OAuth2**: Not supported by Mailpit. For OAuth2 testing, use a real Microsoft 365 tenant.

`TlsMode` **must** be `None`: Mailpit on port 1025 is plain SMTP and does not offer
STARTTLS. `StartTLS` or `SSL` would fail the connection.

## Mailpit API

Mailpit exposes a REST API at `http://localhost:8025/api` for programmatic access:

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/info` | GET | Server info (version, message count) |
| `/api/v1/messages` | GET | List messages (supports `?limit=N`) |
| `/api/v1/message/{id}` | GET | Read a specific message |
| `/api/v1/messages` | DELETE | Delete all messages |
| `/api/v1/search?query=...` | GET | Search messages |

## Verification

1. Check the container is running:

```bash
docker ps --filter name=scadabridge-smtp
```

2. Open the web UI at `http://localhost:8025` to view captured emails.

3. Send a test email using `curl` or any SMTP client:

```bash
# Using Python's smtplib (one-liner)
python3 -c "
import smtplib; from email.mime.text import MIMEText
msg = MIMEText('Test body'); msg['Subject'] = 'Test'; msg['From'] = 'test@example.com'; msg['To'] = 'user@example.com'
smtplib.SMTP('localhost', 1025).sendmail('test@example.com', ['user@example.com'], msg.as_string())
print('Sent')
"
```

## CLI Tool

The `infra/tools/smtp_tool.py` script provides a convenient CLI for interacting with the SMTP server and Mailpit API. This tool uses only Python standard library modules — no additional dependencies required.

**Commands**:

```bash
# Check SMTP connectivity and Mailpit status
python infra/tools/smtp_tool.py check

# Send a test email
python infra/tools/smtp_tool.py send --to user@example.com --subject "Alarm: Tank High Level" --body "Tank level exceeded 95%"

# Send with BCC (matches ScadaBridge notification delivery pattern)
python infra/tools/smtp_tool.py send --to scada-notifications@company.com --bcc "operator1@company.com,operator2@company.com" --subject "Shift Report"

# List captured messages
python infra/tools/smtp_tool.py list

# Read a specific message by ID
python infra/tools/smtp_tool.py read --id <message-id>

# Clear all messages
python infra/tools/smtp_tool.py clear
```

Use `--host` and `--port` to override SMTP defaults (localhost:1025), `--api` for the Mailpit API URL. Run with `--help` for full usage.

## Relevance to ScadaBridge Components

- **Notification Service** — test SMTP delivery, BCC recipient handling, plain-text formatting, and store-and-forward retry behavior (Mailpit can be stopped/started to simulate transient failures).
- **Store-and-Forward Engine** — verify buffered retry by stopping the SMTP container and observing queued notifications.

## Notes

- Mailpit does **not** support OAuth2 Client Credentials authentication. To test the OAuth2 code path, use a real Microsoft 365 tenant (see Q12 in `docs/plans/questions.md`).
- That OAuth2 tenant gap is **superseded for on-prem deployments** by the EWS email transport (`SmtpConfiguration.Transport = Ews`), which submits via Exchange Web Services over HTTPS with Basic auth and needs no tenant — see `docs/plans/2026-08-10-ews-email-transport-design.md`. Q12 is closed as superseded; the OAuth2 SMTP path stays config-selectable but untested against a live tenant.
- EWS has **no local test container**: its unit tests drive an in-process stub endpoint (envelope shape, Basic header, canned success / SOAP-fault / HTTP-error bodies), and the live gate against a real Exchange server is a **manual, one-off** exercise. Mailpit remains the SMTP-path harness and is unaffected.
- To simulate SMTP failures for store-and-forward testing, stop the container: `docker compose stop smtp`. Restart with `docker compose start smtp`.
- The web UI at `http://localhost:8025` provides real-time message inspection, search, and message source viewing.
- No data persistence — messages are stored in a temporary database inside the container and lost on container removal.
