1## Design goals23- **Make it easy to deploy.**4 Minimal configuration changes should be required to get a typical mail server5 running. Though, it is important to avoid making guesses for a6 "zero-configuration". A wrong guess is worse than no guess.78- **Provide 80% of needed components.**9 E-mail has evolved into a huge mess. With a single package to do one thing, it10 quickly turns into a maintenance nightmare. Put all stuff mail server11 typically needs into a single package. Though, leave controversial or highly12 opinionated stuff out, don't force people to do things our way13 (see next point).1415- **Interoperate with existing software.**16 Implement (de-facto) standard protocols not only for clients but also for17 various server-side helper software (content filters, etc).1819- **Be secure but interoperable.**20 Verify DKIM signatures by default, use DMRAC policies by default, etc. This21 makes default setup as secure as possible while maintaining reasonable22 interoperability. Though, users can configure maddy to be stricter.2324- **Achieve flexibility through composability.**25 Allow connecting components in arbitrary ways instead of restricting users to26 predefined templates.2728- **Use Go concurrency features to the full extent.**29 Do as much I/O as possible in parallel to minimize latencies. It is silly to30 not do so when it is possible.3132## Design summary3334Here is a summary of how things are organized in maddy in general. It explains35things from the developer perspective and is meant to be used as an36introduction by the new developers/contributors. It is recommended to read37user documentation to understand how things work from the user perspective as38well.3940- User documentation: [maddy.conf(5)](docs/man/maddy.5.scd)41- Design rationale: [Comments on design (Wiki)][1]4243There are components called "modules". They are represented by objects44implementing the module.Module interface. Each module gets its unique name.45The function used to create a module instance is saved with this name as a key46into the global map called "modules registry".4748Whenever module needs another module for some functionality, it references it49using a configuration directive with a matcher that internally calls50`modconfig.ModuleFromNode`. That function looks up the module "constructor" in51the registry, calls it with corresponding arguments, checks whether the52returned module satisfies the needed interfaces and then initializes it.5354Alternatively, if configuration uses &-syntax to reference existing55configuration block, `ModuleFromNode` simply looks it up in the global instances56registry. All modules defined the configuration as a separate top-level blocks57are created before main initialization and are placed in the instances registry58where they can be looked up as mentioned before.5960Top-level defined module instances are initialized (`Init` method) lazily as61they are required by other modules. 'smtp' and 'imap' modules follow a special62initialization path, so they are always initialized directly.6364## Error handling6566Familiarize yourself with the `github.com/foxcpp/maddy/framework/exterrors`67package and make sure you have the following for returned errors:68- SMTP status information (smtp\_code, smtp\_enchcode, smtp\_msg fields)69 - SMTP message text should contain a generic description of the error70 condition without any details to prevent accidental disclosure of the71 server configuration details.72- `Temporary() == true` for temporary errors (see `exterrors.WithTemporary`)73- Field that includes the module name7475The easiest way to get all of these is to use `exterrors.SMTPError`.76Put the original error into the `Err` field, so it can be inspected using77`errors.Is`, `errors.Unwrap`, etc. Put the module name into `CheckName` or78`TargetName`. Add any additional context information using the `Misc` field.79Note, the SMTP status code overrides the result of `exterrors.IsTemporary()`80for that error object, so set it using `exterrors.SMTPCode` that uses81`IsTemporary` to select between two codes.8283If the error you are wrapping contains details in its structure fields (like84`*net.OpError`) - copy these values into `Misc` map, put the underlying error85object (`net.OpError.Err`, for example) into the `Err` field.86Avoid using `Reason` unless you are sure you can provide the error message87better than the `Err.Error()` or `Err` is `nil`.8889Do not attempt to add a SMTP status information for every single possible90error. Use `exterrors.WithFields` with basic information for errors you don't91expect. The SMTP client will get the "Internal server error" message and this92is generally the right thing to do on unexpected errors.9394### Goroutines and panics9596If you start any goroutines - make sure to catch panics to make sure severe97bugs will not bring the whole server down.9899## Adding a check100101"Check" is a module that inspects the message and flags it as spam or rejects102it altogether based on some condition.103104The skeleton for the stateful check module can be found in105`internal/check/skeleton.go`. Throw it into a file in106`internal/check/check_name` directory and start ~~breaking~~ extending it.107108If you don't need any per-message state, you can use `StatelessCheck` wrapper.109See `check/dns` directory for a working example.110111Here are some guidelines to make sure your check works well:112- RTFM, docs will tell you about any caveats.113- Don't share any state _between_ messages, your code will be executed in114 parallel.115- Use `github.com/foxcpp/maddy/check.FailAction` to select behavior on check116 failures. See other checks for examples on how to use it.117- You can assume that order of check functions execution is as follows:118 `CheckConnection`, `CheckSender`, `CheckRcpt`, `CheckBody`.119120## Adding a modifier121122"Modifier" is a module that can modify some parts of the message data.123124Note, currently this is not possible to modify the body contents, only header125can be modified.126127Structure of the modifier implementation is similar to the structure of check128implementation, check `modify/replace\_addr.go` for a working example.129130[1]: https://github.com/foxcpp/maddy/wiki/Dev:-Comments-on-design