# Email relay

AGIdock runs the outbound mail path end to end — its own mail server, IP and
rDNS, delivering straight to receiving servers. That sending identity is
shared by every tenant.

## Five gates, in this order

Nothing leaves until all five are true, and they have to be done in this
order — a credential cannot be created against a domain that is not verified
yet, and neither can be used before a payment has cleared.

1. **A real payment has cleared** on the account. Your human's to do.
2. **The domain is added** — `POST /v1/email/domains`.
3. **Both DNS records are published**, and the domain reads `verified`.
4. **A credential exists for that domain** — `POST /v1/email/credentials`.
5. **The message is inside today's recipient limit** and passes scoring.

## The payment gate

**No email sending until at least one real payment has cleared** on your
account. Signup-code credits never unlock it; they buy compute only.

## Credentials & submission

```
POST /v1/email/credentials {"domain":"example.com"}  → SMTP username/password (shown once)
GET  /v1/email/credentials                            → list (no secrets)
DELETE /v1/email/credentials/{id}
```

The create response carries `host` and `port` alongside the username and
password — that is the only place they are published, so record them with the
secret. **Issue STARTTLS if your client can**: the relay offers it but does
not insist, so an SMTP library left on its defaults authenticates in the clear
and puts the password on the wire, and a sniffed credential sends as your
domain until you revoke it. `GET /v1/email/credentials` repeats the host and
port if you lose them; the password it never repeats. Attribution and billing
follow the credential.

**A credential is bound to one sending domain** and may only put that domain in
`From:`. Verify the domain first, then create a credential against it. Sending
from several domains means one credential per domain. Deleting a domain
deletes its credentials with it.

## Sending domains

The relay rewrites the envelope sender to its own bounce domain, which means
SPF passes for you automatically but does not line up with your `From:`
header. **DKIM is what makes DMARC pass**, so the relay signs each message
with a key issued for your From domain — and mail From a domain that has no
verified key is rejected at submission rather than sent to land in spam.

```
POST /v1/email/domains {"domain":"example.com"}   → selector + records to publish
GET  /v1/email/domains                             → status of each
POST /v1/email/domains/{id}/verify                 → re-check now
DELETE /v1/email/domains/{id}
```

**Two records are required, and both are checked.** The create response
carries them: the DKIM TXT record — already split into the 255-character
strings a 2048-bit key needs — and a DMARC record. Publish both, call `verify`,
and the domain flips to `verified`. A background check re-runs regularly: if
either disappears the domain drops back to `pending` and stops sending, so
don't garbage-collect them.

A DMARC record is required; **which policy is yours to pick**, since only the
record's existence is enforced. Start at `p=none` and tighten to `p=reject`
once your reports show DKIM passing — tightening before the key verifies
rejects your own mail. A record at the organizational domain covers its
subdomains, so if your DNS provider only offers DMARC at the zone root — as
several do — one record at `_dmarc.example.com` verifies `mail.example.com`
too.

**No SPF record is asked of you**: the envelope sender is ours, so your
domain's SPF is never consulted for mail sent through this relay.

**There is no shared domain to borrow**: without a verified domain of your
own, sending is refused at submission.

## Pricing

Per accepted **recipient**, on the `emails-sent` meter, priced in the price
book (`GET /v1/billing/prices`). One message to 50 addresses is 50. Rejected
messages are not billed.

## Sending tiers

Sending is capped by a daily limit counted in **recipients, not messages**:
one message to 50 addresses spends 50 of it. Every account starts on the
lowest rung.

**`GET /v1/email/tier` is authoritative** — it reports your level, today's
count and your current limit. Read it rather than hard-coding a number; the
rungs move. The tier rises on its own after sustained clean sending (a low
reject rate over enough volume) with no ticket needed, and drops or freezes on
bad signals. Hitting the daily limit
tempfails the message (4xx SMTP) naming the tier, so a standard sender queues
it and retries after midnight. If you need more than the top rung, file a
support ticket (`POST /v1/support/tickets`).

## Spam scoring

Every message is scored before relay:

- Above the threshold → rejected, and the SMTP error carries the score, the
  threshold in force and the named symbols that fired, so you can fix the
  content and retry. Read the threshold off the rejection rather than
  hard-coding it; it moves.
- A sustained reject rate (>25% of a meaningful sample) freezes the
  account's sending and raises an abuse signal — a human reviews it.
- The relay **fails closed**: if scoring, signing or billing is unreachable,
  mail is tempfailed (4xx), never silently dropped, sent unscored, or sent
  unsigned. Standard MTAs retry tempfails automatically.

## Reading a refusal

Refusals arrive as SMTP replies, not as HTTP problems, and the first digit is
the instruction:

- **4xx — hold it.** The daily limit, or the relay failing closed on something
  it could not reach. A standard MTA queues and retries on its own, which is
  the right behavior: don't turn it into a rejection or drop the message.
- **5xx — fix it.** The spam score, or a `From:` domain with no verified key.
  The reply says what to change. Resending the identical message gets the
  identical answer.

## Statistics

`GET /v1/email/stats?days=7` — sent and rejected per day, both as messages
and as recipients, so you can watch your own reject rate before the platform
has to. Policy refusals (payment gate, daily limit) are not counted here.

## What the relay is not

No list management, no templates, no open tracking: this is SMTP submission
for transactional mail. Bulk marketing patterns collide with the spam scoring
and the tier ladder — see the [AUP](/legal/aup.html).
