|
| 1 | +# AGENTS.md — Maddy Mail Server |
| 2 | + |
| 3 | +## Architecture |
| 4 | + |
| 5 | +Maddy is a composable all-in-one mail server (MTA/MX/IMAP) written in Go. The core abstraction is the **module system**: every functional component (auth, storage, checks, targets, endpoints) implements `module.Module` from `framework/module/module.go` and registers itself via `module.Register(name, factory)` in an `init()` function. |
| 6 | + |
| 7 | +- **`framework/`** — Stable, reusable packages (config parsing, module interfaces, address handling, error types, logging). Interfaces live here to avoid circular imports. |
| 8 | +- **`internal/`** — All module implementations. Subdirectories map to module roles: `endpoint/` (protocol listeners), `target/` (delivery destinations), `auth/`, `check/` (message inspectors), `modify/` (header modifiers), `storage/`, `table/` (string→string lookups). |
| 9 | +- **`maddy.go`** — Side-effect imports that pull all `internal/` modules into the binary, plus the `Run`/`moduleConfigure`/`RegisterModules` startup sequence. |
| 10 | +- **`cmd/maddy/main.go`** — Thin entrypoint; imports root package for module registration, then calls `maddycli.Run()`. |
| 11 | + |
| 12 | +Modules are wired together at runtime via `maddy.conf` configuration. Top-level blocks are lazily initialized through `module.Registry`. The **message pipeline** (`internal/msgpipeline/`) routes messages from endpoints through checks, modifiers, and to delivery targets based on sender/recipient matching rules. |
| 13 | + |
| 14 | +## Build & Test |
| 15 | + |
| 16 | +```sh |
| 17 | +# Build (produces ./build/maddy by default): |
| 18 | +./build.sh build |
| 19 | + |
| 20 | +# Build with specific tags (e.g. for Docker): |
| 21 | +./build.sh --tags "docker" build |
| 22 | + |
| 23 | +# Unit tests (standard Go): |
| 24 | +go test ./... |
| 25 | + |
| 26 | +# Integration tests |
| 27 | +cd tests && ./run.sh |
| 28 | +``` |
| 29 | + |
| 30 | +The build embeds version via `-ldflags -X github.com/foxcpp/maddy.Version=...`. A C compiler is needed for SQLite support (`mattn/go-sqlite3`). |
| 31 | + |
| 32 | +## Adding a New Module |
| 33 | + |
| 34 | +1. Create a package under the appropriate `internal/` subdirectory (e.g. `internal/check/mycheck/`). |
| 35 | +2. Implement `module.Module` plus the relevant role interface (`module.Check`, `module.DeliveryTarget`, `module.PlainAuth`, `module.Table`, etc.) from `framework/module/`. |
| 36 | +3. Register in `init()`: `module.Register("check.mycheck", NewMyCheck)`. Use naming convention: `check.`, `target.`, `auth.`, `table.`, `modify.` prefixes. |
| 37 | +4. Add a blank import `_ "github.com/foxcpp/maddy/internal/check/mycheck"` in `maddy.go`. |
| 38 | +5. For checks: use the skeleton at `internal/check/skeleton.go` or `check.RegisterStatelessCheck` (see `internal/check/dns/` for a stateless example). |
| 39 | + |
| 40 | +## Error Handling |
| 41 | + |
| 42 | +Use `framework/exterrors` — not bare `fmt.Errorf`. Errors crossing module boundaries must carry: |
| 43 | +- SMTP status info via `exterrors.SMTPError{Code, EnhancedCode, Message, CheckName/TargetName}` |
| 44 | +- Temporary flag via `exterrors.WithTemporary` |
| 45 | +- Module name field |
| 46 | + |
| 47 | +Keep SMTP error messages generic (no server config details). Use `exterrors.WithFields` for unexpected errors. See `HACKING.md` for full guidelines. |
| 48 | + |
| 49 | +## Key Conventions |
| 50 | + |
| 51 | +- **No shared state between messages** — check/modifier code runs in parallel across messages. |
| 52 | +- **Panic recovery** — any goroutine you spawn must recover panics to avoid crashing the server. |
| 53 | +- **Address normalization** — domain parts must be U-labels with NFC normalization and case-folding. Use `framework/address.CleanDomain`. |
| 54 | +- **Configuration parsing** — modules receive config via `config.Map` in their `Configure` method. See `framework/config/` and existing modules for the pattern. |
| 55 | +- **Logging** — use `framework/log.Logger`, not `log` stdlib. Per-delivery loggers via `target.DeliveryLogger(...)`. |
| 56 | + |
0 commit comments