Skip to main content

postcrate_core/
lib.rs

1//! Tokio-native SMTP capture engine for local development, integration
2//! tests, and CI.
3//!
4//! `postcrate-core` listens for SMTP, parses incoming mail, stores it in
5//! SQLite, and exposes everything over a small HTTP API. It has no
6//! dependency on a UI or on any third-party mail service.
7//!
8//! # Quick start
9//!
10//! ```no_run
11//! # async fn run() -> anyhow::Result<()> {
12//! use std::sync::Arc;
13//! use postcrate_core::{CoreConfig, LogSink, Service};
14//!
15//! let cfg = CoreConfig::for_data_dir("/tmp/postcrate")?;
16//! let service = Service::build(cfg, Arc::new(LogSink)).await?;
17//! service.start_all().await?;
18//! # service.stop_all().await?;
19//! # Ok(()) }
20//! ```
21//!
22//! After [`Service::start_all`], the configured SMTP listeners are
23//! accepting mail and the HTTP API is serving requests under `/api/v1`.
24//! Subscribe to live events by passing a custom [`EventSink`] instead of
25//! [`LogSink`].
26//!
27//! # Public surface
28//!
29//! The public API is deliberately narrow: the [`Service`] type plus a
30//! handful of input / output structs re-exported from this module.
31//! Everything else is `pub(crate)`. See the [`Service`] type for the
32//! full method set, and the `postcrate-docs` repository for the
33//! architecture notes, HTTP-API reference, and SMTP-extension table.
34//!
35//! # Crate features
36//!
37//! | Feature | Effect |
38//! |---------|--------|
39//! | `tls`   | Enables `STARTTLS` and implicit-TLS listeners (RFC 8314). Off by default. |
40
41#![forbid(unsafe_code)]
42#![warn(missing_debug_implementations)]
43#![warn(clippy::pedantic)]
44// Several public-API items (TLS scaffolding, less-common reply helpers,
45// diagnostic fields on listener handles) read as dead-code to the
46// compiler because their consumers live in downstream binaries and
47// integration tests rather than the library crate itself.
48#![allow(dead_code)]
49#![allow(clippy::module_name_repetitions)]
50#![allow(clippy::missing_errors_doc)]
51#![allow(clippy::missing_panics_doc)]
52#![allow(clippy::must_use_candidate)]
53#![allow(clippy::cast_possible_truncation)]
54#![allow(clippy::cast_sign_loss)]
55#![allow(clippy::cast_precision_loss)]
56#![allow(clippy::similar_names)]
57#![allow(clippy::too_many_lines)]
58
59pub mod config;
60pub mod error;
61pub mod events;
62pub mod matcher;
63pub mod recording;
64pub mod rendering;
65pub mod scenarios;
66pub mod service;
67pub mod tagging;
68
69pub(crate) mod db;
70pub(crate) mod http;
71pub(crate) mod mail;
72pub(crate) mod mailbox;
73pub(crate) mod pipeline;
74pub(crate) mod smtp;
75
76pub use crate::config::{BindHost, CoreConfig};
77
78/// Stable, panic-safe wrappers around internal parsers used by the
79/// cargo-fuzz targets under `fuzz/`. Not intended for general use;
80/// the public surface for normal callers is the [`Service`] type.
81#[doc(hidden)]
82pub mod fuzz {
83    /// Run the SMTP command-line parser. Returns `Ok` or `Err`; either
84    /// way, the parser must not panic.
85    pub fn parse_smtp_command(input: &str) -> std::result::Result<(), String> {
86        crate::smtp::command::SmtpCommand::parse(input)
87            .map(|_| ())
88            .map_err(|e| e.to_string())
89    }
90
91    /// Run the MIME parser. Must not panic on arbitrary input.
92    pub fn parse_mail(bytes: &[u8]) {
93        let _ = crate::mail::parse::parse(bytes);
94    }
95
96    /// Run the SMTP path parser (the inside of `MAIL FROM:<...>`).
97    pub fn parse_smtp_path(input: &str) -> std::result::Result<(), String> {
98        crate::mail::address::parse_path(input)
99            .map(|_| ())
100            .map_err(|e| e.to_string())
101    }
102}
103pub use crate::db::audit::AuditEntry;
104pub use crate::db::bounce_rules::BounceRule;
105pub use crate::db::chaos_configs::ChaosConfig;
106pub use crate::db::emails::{AttachmentMeta, EmailDetail, EmailSummary};
107pub use crate::db::forwarding::{CreateForwardingRule, ForwardingRule};
108pub use crate::db::mailboxes::{
109    CreateEphemeralInput, CreateMailboxInput, EphemeralHandle, Mailbox, UpdateMailboxInput,
110};
111pub use crate::db::webhooks::{CreateWebhook, Webhook};
112pub use crate::db::settings::{
113    AdvancedPrefs, AgentPrefs, BackendSettings, InboxPrefs, NetworkPrefs, SettingsPatch,
114    SettingsSection,
115};
116pub use crate::error::{Error, Result};
117pub use crate::events::{
118    BounceKind, ChannelSink, CoreEvent, EventSink, LogSink, MailboxStateChange, ServerStatus,
119};
120pub use crate::mailbox::kinds::MailboxKind;
121pub use crate::matcher::{EmailPredicate, HeaderPredicate, MatchResult, WaitOutcome};
122pub use crate::recording::{Recording, RecordedEnvelope, RecordedMessage, RECORDING_VERSION};
123pub use crate::rendering::a11y::{A11yFinding, A11yReport};
124pub use crate::rendering::lint::{LintReport, LintWarning};
125pub use crate::rendering::profile::{Fidelity, Profile, RenderedPreview};
126pub use crate::scenarios::auth::{AuthReport, AuthVerdict};
127pub use crate::scenarios::links::{DetectedLink, LinkCounts, LinkKind, LinkReport};
128pub use crate::scenarios::list_unsub::{UnsubFinding, UnsubReport, UnsubUri};
129pub use crate::scenarios::spam::{SpamFactor, SpamReport, SpamVerdict};
130pub use crate::smtp::relay::RelayConfig;
131pub use crate::tagging::EmailTag;
132pub use crate::service::Service;