# Concepts

## Tenant (workspace / practice)

A **practice** is one isolated sending tenant. It has its own:

- Sending domain and DKIM tokens
- Projects and routes
- Suppressions and consent ledger
- Webhooks, inbound mailbox, and API tokens
- Monthly plan cap and reputation counters

One tenant’s bounce or complaint rate never applies to another.

## Projects

Projects group routes and tokens inside a tenant. A default project is seeded on create. Use extra projects to separate brands, clinics, or environments.

Project-scoped API tokens (`wm_proj_…`) can send only for that project. Team tokens (`wm_team_…`) can manage the workspace.

## Routes

Each tenant gets two built-in routes:

| Slug | Type | Use |
| --- | --- | --- |
| `outgoing` | transactional | Appointments, invoices, password resets, DOI mail |
| `broadcast` | marketing | Newsletters and campaigns |

You can add more routes (`POST /api/practices/{id}/routes`) and assign them to a project. `{routeID}` in APIs accepts a UUID **or** the slug (`outgoing`, `broadcast`).

Route settings:

- `track_opens` / `track_clicks` — default off for clinical/transactional kinds
- `plain_text_fallback` — generate a text part from HTML when you omit `text`

## Message kinds

`message_kind` drives consent checks, default tracking, and legal-footer rules:

| Kind | Typical route | Default tracking | Legal basis hint |
| --- | --- | --- | --- |
| `termin` | `outgoing` | off | contract |
| `recall` | `outgoing` | off | contract |
| `transactional` | `outgoing` | off | contract |
| `doi` | `outgoing` | off | consent |
| `newsletter` | `broadcast` | on | consent |
| `custom` | `broadcast` | on | consent |
| `quality` / `test` | `outgoing` | off | contract |

`ValidateRouteMessageKind` rejects newsletter on transactional routes and appointment kinds on broadcast.

## Consent and channels

Preferences are stored per **channel** (`termin`, `recall`, `newsletter`, `transactional`, `other`). Newsletter requires double opt-in evidence (IP, wording, timestamps) before broadcast.

Every outbound message — including transactional — receives a one-click unsubscribe URL when `PUBLIC_BASE_URL` is a stable HTTPS origin. Recipients can opt out of **all** mail from that sender if a tenant misuses transactional mail for marketing.

## Suppressions

Addresses can be blocked at **team** (whole tenant), **project**, or **route** scope. Hard bounces and complaints auto-suppress at team scope. List-unsubscribe events suppress at route scope when the message had a route.

## Plans

| Plan | Cap | Notes |
| --- | --- | --- |
| Test | 200 / month | Manual approval, powered-by footer, blocks all sends at 100% |
| Supporter | 1,000 / month | €5, first live paid plan |
| Termin / Praxis / Gruppe | 5k / 20k / 100k | Waiting list or admin-assigned |

At 70% / 90% / 100% of the monthly cap, owners get one email per threshold per UTC month. On paid plans, broadcast pauses at 100%; transactional kinds can still send. Reputation pause is separate.
