mailrs_inbound/lib.rs
1#![deny(missing_docs)]
2#![deny(rustdoc::broken_intra_doc_links)]
3#![doc = include_str!("../README.md")]
4
5//! Composable SMTP receive pipeline framework for Rust mail servers.
6//!
7//! ## What's in the box
8//!
9//! - [`Stage`] trait — one async check that reads + mutates the receive
10//! context and returns whether to continue or short-circuit with a
11//! decision.
12//! - [`Pipeline`] + [`PipelineBuilder`] — sequential executor with
13//! early-reject.
14//! - [`ReceiveContext`] — accumulator for static request data + the
15//! signals each stage contributes (auth results, virus, content score,
16//! PTR score, AI score).
17//! - [`DeliveryDecision`] — the four-variant outcome
18//! (`Accept` / `Junk` / `Reject` / `Greylist`).
19//! - [`make_delivery_decision`] — pure final-decision policy combiner.
20//! Callable directly if you don't want the [`Pipeline`] framework.
21//! - [`build_auth_header`] / [`AuthResult`] / [`format_auth_results`] —
22//! RFC 8601 `Authentication-Results:` header builders.
23//!
24//! ## What's NOT in the box
25//!
26//! By design, the crate ships zero protocol / backend code:
27//!
28//! - No SPF / DKIM / DMARC verifier — wrap your favorite crate
29//! (`mail-auth`, `dkim-rs`, ...) inside your own [`Stage`] impl.
30//! - No virus scanner — ClamAV, rspamd, whatever your stack uses.
31//! - No greylisting backend — Redis / Memcached / Postgres / in-memory.
32//! - No DNS resolver type.
33//! - No LLM / ML scoring provider.
34//!
35//! That means **the crate is small, dependency-light, and trivial to drop
36//! into any existing SMTP server**. The price is that every consumer
37//! writes their own [`Stage`] impls — typically 50-150 LOC per stage,
38//! see the README for example shapes.
39//!
40//! ## Sketch
41//!
42//! ```ignore
43//! use mailrs_inbound::{Pipeline, ReceiveContext, Stage, StageOutcome};
44//! use async_trait::async_trait;
45//!
46//! struct MyGreylistStage { /* ... */ }
47//!
48//! #[async_trait]
49//! impl Stage for MyGreylistStage {
50//! fn name(&self) -> &str { "greylist" }
51//! async fn evaluate(&self, ctx: &mut ReceiveContext) -> StageOutcome {
52//! // ... your greylist logic ...
53//! StageOutcome::Continue
54//! }
55//! }
56//!
57//! # async fn build_and_run(stage: MyGreylistStage, mut ctx: ReceiveContext) {
58//! let pipeline = Pipeline::builder()
59//! .add(stage)
60//! // ... more stages ...
61//! .build();
62//!
63//! let decision = pipeline.run(&mut ctx).await;
64//! # let _ = decision;
65//! # }
66//! ```
67
68pub mod auth_header;
69pub mod context;
70pub mod decision;
71pub mod pipeline;
72pub mod stage;
73
74// Public re-exports — the surface most consumers reach for.
75pub use auth_header::{
76 build_auth_header, format_auth_results, format_auth_results_header, AuthResult,
77};
78pub use context::{AuthResults, DmarcPolicy, ReceiveContext};
79pub use decision::{make_delivery_decision, DeliveryDecision, PipelineInput};
80pub use pipeline::{Pipeline, PipelineBuilder, DEFAULT_SPAM_THRESHOLD};
81pub use stage::{Stage, StageOutcome};