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

# Outbound relay & smarthost

> Send outbound mail through a relay (smarthost or ESP) when your IP is cold or port 25 is blocked, and run OxiMail as a smarthost for other instances: relay accounts, DNS-proven domains, one-time enrolment, ephemeral retention, and reputation monitoring.

A freshly installed mail server sends from a **cold IP**. Large receivers throttle or junk mail from an IP with no history, and most clouds and ISPs **block outbound port 25 entirely** — so a new deployment often cannot deliver at all on day one. The answer is the same one Postfix has offered for twenty-five years: keep receiving and storing your own mail, but route outbound through a **relay** (a *smarthost*) whose IP is already warm.

OxiMail treats this as two roles, and one server can play either:

* **The leaf** relays its outbound *through* a smarthost. It is still a normal Primary mail server — only how it *sends* changes.
* **The smarthost** *accepts* relayed mail from leaves. This is a capability of the Primary role, not a separate install mode.

<Note>
  Relaying is entirely opt-in. An instance that sends direct is unaffected by anything on this page — no configuration, no behaviour change.
</Note>

## Part 1 — Sending through a relay (the leaf)

### Choosing the mode at install

`oximail setup` asks, after network topology, for your **outbound send mode**. It probes outbound port 25 and recommends **relay** when direct delivery is impossible. The choice is neutral — the relay can be a managed smarthost service, an ESP (Amazon SES, SMTP2GO…), or an internal company smarthost; OxiMail asks for its connection details generically and never pushes a particular service.

You configure the relay one of two ways:

* **Enter the details manually** — the relay host, port, SMTP AUTH username and password, and the SPF term to publish. Works for any relay.
* **Redeem an enrolment token** — for a managed smarthost that hands out onboarding tokens. You paste the smarthost's URL and a one-time token; the wizard fetches the credentials automatically. See [Part 2](#one-time-enrolment).

### What it writes

A relay is a catch-all `[[smtp.transport_maps]]` route. The AUTH password is written to a **sidecar file** (mode `0600`), never inline in the TOML:

```toml theme={null}
[[smtp.transport_maps]]
pattern = "*"
relay_host = "smtp.your-relay.example"
relay_port = 587
auth_user = "your-relay-account"
auth_password_file = "/etc/oximail/relay.password"
implicit_tls = false          # true for port 465
spf_mechanism = "include:spf.your-relay.example"
```

Delivery to the relay is **TLS-mandatory by construction**: an authenticated route has no plaintext fallback, the relay's certificate is WebPKI-verified, and credentials are never sent over an unencrypted connection.

<Warning>
  The wizard **refuses to activate a relay route until your SPF and DKIM checks pass**. A relay whose SPF story is not published will send mail that receivers mark as spam — the gate exists to stop exactly that silent failure. Publish the DNS below first, or pass `--force` while DNS propagates.
</Warning>

### The DNS you must publish

Two records on each **sender** domain:

| Record   | Value                                                          | Why                                                                                                                                                                        |
| -------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SPF**  | add `include:spf.your-relay.example` to your `v=spf1 …` record | Authorizes the relay's egress IP to send for your domain. Use the relay's `include:` (not a hard `ip4:`) so the relay operator can renumber without you editing your zone. |
| **DKIM** | your own `d=yourdomain` key (unchanged)                        | The relay forwards your message **verbatim** — your signature is preserved, so DMARC passes on DKIM regardless of which IP emits the mail.                                 |

A managed smarthost additionally asks you to publish a **relay-domain proof** — `_oximail-relay.yourdomain TXT "<token>"` — which proves you control the domain before the smarthost will emit mail for it. The onboarding provides the token.

### Going direct later

Relaying is a supported permanent setup. If you want to cut over to direct sending once your own IP is warm, check readiness first:

```bash theme={null}
oximail setup relay-preflight --ip <your-egress-ip>
```

It verifies outbound port 25 is reachable, that your reverse DNS is **forward-confirmed** (the PTR resolves back to the IP), and that the IP is not on a public DNSBL. Exit `0` means ready — remove the catch-all `[[smtp.transport_maps]]` route and send direct. The cutover is reversible.

## Part 2 — Running a smarthost

A smarthost accepts authenticated relay from leaf instances. Enabling it is a matter of creating the relay accounts that are allowed to relay — there is no separate boot role or install path. For a **dedicated** hub, the setup wizard has a `Smarthost relay (dedicated hub)` deploy-mode entry ([first boot](/first-boot#smarthost-flow)): the same primary flow with curated deltas — egress IPs collected, the hub SPF record published, mailbox surfaces off, and a `[mode] profile = "smarthost"` **declaration** the boot audit checks against the config on every start (it warns if mailbox surfaces creep back on or the DNSBL self-monitor is off; it never changes behavior). A combined mailbox-plus-hub server simply leaves the profile unset.

### Relay accounts

A relay account is an account with role `service`. Unlike a mailbox user, it owns no mail; it exists to authenticate an SMTP relay session. Create one and mark it a service principal:

```bash theme={null}
oximail account create --email relay-client@your-smarthost.example --role service --tenant-id <tenant>
oximail account set-role --account relay-client@your-smarthost.example --role service
```

### Proven declared domains

A relay account may only send from domains it has **proven it controls**. This is the entire security boundary — without it, one client could send as another's domain from your shared IP.

```bash theme={null}
oximail account relay add-domain --email relay-client@your-smarthost.example --domain client.tld
# → prints the DNS proof token to publish:
#   _oximail-relay.client.tld  TXT  "<token>"
```

The client publishes that TXT record; you then verify it:

```bash theme={null}
oximail account relay verify --email relay-client@your-smarthost.example
oximail account relay list   --email relay-client@your-smarthost.example    # → "active"
```

A background worker re-verifies every declared domain every six hours and **suspends it fail-closed** if the proof disappears. A transient DNS failure never suspends — only a definitive "no such record" answer does, so a resolver blip cannot knock a client offline.

<Note>
  An account with no verified domain relays **nothing** — the ingress is deny-by-default. The null sender (`<>`) is allowed so bounce notices can transit.
</Note>

### One-time enrolment

Instead of handing a client raw credentials, mint a single-use token. The client's wizard redeems it and receives its credentials automatically.

```bash theme={null}
oximail account relay mint-token --email relay-client@your-smarthost.example --tenant-id <tenant> --ttl-hours 24
```

The token is printed once (only its hash is stored). The client redeems it at `POST /relay/enrol` — the endpoint validates and consumes the token, provisions the `service` account, and returns the credentials one time. A leaked token is revoked with `oximail account relay revoke-tokens`.

### Recipient quotas and warm-up

On a shared egress IP the reputation-critical unit is the **recipient**, not the message. Each relay account has a durable per-day recipient tally with a warm-up ramp: a new account starts at 200 recipients/day and doubles weekly toward `[rate_limit] relay_max_rcpt_per_day` (default 5000). Over the cap, a submission gets a transient `452` and the sender's queue retries — never a bounce.

### The SPF term you publish

Publish an `spf.your-smarthost.example` TXT record listing your egress IP(s), and hand clients `include:spf.your-smarthost.example`. The indirection lets you renumber egress IPs without every client editing their zone. The dedicated-hub wizard flow publishes this record for you (through Cloudflare when a token is provided, printed for manual publication otherwise).

### Retention: the relay is not an archive

Mail relayed by a `service` account is **ephemeral**. At the destination's `250` acceptance, its queue row and its encrypted blob are purged — the identifying record is destroyed and the blob's encryption key dropped (the content becomes undecryptable at that instant). On the relay path, delivery logs carry the queue id only, never envelope addresses. A non-identifying per-day purge counter is the audit trail. Non-relay outbound is unaffected — it keeps its delivered record.

### Verbatim forwarding and extension mediation

Two wire-level guarantees shape what a relay hop may and may not do to a message:

* **The relay never rewrites, and never re-signs.** A relayed message is forwarded byte-identical — including when the smarthost happens to host the `From:` domain itself. No smarthost `DKIM-Signature` is added, so the client's own signature remains the only one and stays verifiable end to end (tamper evidence: any modification in transit is detectable).
* **ESMTP requirements are mediated, not bridged.** Every outbound hop — leaf to relay, relay to destination — derives what the message needs from its content (a non-ASCII address or header requires SMTPUTF8, a high-bit body octet requires 8BITMIME, the size is declared when the peer accepts `SIZE=`) and checks the destination's advertised capabilities. A gap that cannot be bridged bounces permanently (`550 5.6.7` / `554 5.6.3` / `552 5.3.4`) instead of delivering a mis-encoded message. For the same reason the inbound EHLO does not advertise DSN parameters or BINARYMIME onward promises it cannot keep: DSN `NOTIFY`/`ORCPT` parameters are stripped rather than silently lost downstream, and `BODY=BINARYMIME` is rejected `555 5.5.4`.

### Reputation monitoring

A listed shared egress IP degrades delivery for every client at once. Turn on self-monitoring:

```toml theme={null}
[metrics]
dnsbl_self_ips = ["203.0.113.10"]   # your egress IP(s)
```

A worker checks each IP against Spamhaus ZEN, Barracuda, and SpamCop every fifteen minutes and publishes `oximail_smarthost_ip_listed{ip,list}`. Alert on `> 0` and enrol your IPs in receiver feedback programs (Google Postmaster Tools, Microsoft SNDS/JMRP, Yahoo CFL) **before** your first client.

<Warning>
  A shared smarthost carries the abuse and reputation responsibility for everything it relays. Monitor the egress IP, rate-limit per account, and — for a managed service — put the data-processing terms in writing (a relay operator is a data processor for the mail it transmits).
</Warning>
