reinhardt_mail/lib.rs
1//! # Reinhardt Email
2//!
3//! Django-style email sending for Reinhardt with comprehensive features for production use.
4//!
5//! ## Features
6//!
7//! ### Core Message Building
8//! - **EmailMessage**: Flexible email message builder with fluent API
9//! - **Alternative Content**: Support for multiple content representations (HTML, plain text)
10//! - **Attachments**: File attachments with automatic MIME type detection
11//! - **Inline Images**: Embed images in HTML emails using Content-ID
12//! - **CC/BCC/Reply-To**: Full support for email headers
13//! - **Custom Headers**: Add custom email headers
14//!
15//! ### Multiple Backends
16//! - **SMTP Backend**: Production-ready SMTP with TLS/SSL support
17//! - STARTTLS and direct TLS/SSL connections
18//! - Multiple authentication mechanisms (PLAIN, LOGIN, Auto)
19//! - Configurable connection timeout
20//! - **Console Backend**: Development backend that prints to console
21//! - **File Backend**: Save emails to files for testing
22//! - **Memory Backend**: In-memory storage for unit tests
23//!
24//! ### Template System
25//! - **Template Integration**: Simple template rendering with context
26//! - **Dynamic Content**: Generate emails from templates with variable substitution
27//! - **HTML and Text**: Support for both HTML and plain text templates
28//!
29//! ### Email Validation
30//! - **RFC 5321/5322 Compliance**: Validate email addresses
31//! - **Header Injection Protection**: Prevent email header injection attacks
32//! - **Domain Validation**: IDNA support for international domains
33//! - **Sanitization**: Normalize and clean email addresses
34//!
35//! ### Bulk Operations
36//! - **Connection Pooling**: Efficient connection management for bulk sending
37//! - **Batch Sending**: Send emails in batches with rate limiting
38//! - **Concurrent Sending**: Parallel email delivery with configurable concurrency
39//! - **Mass Mail**: Send multiple emails efficiently
40//!
41//! ### Async Support
42//! - **Fully Async**: All operations use async/await
43//! - **Tokio Integration**: Built on Tokio runtime
44//! - **Non-blocking**: No blocking operations in the async path
45//!
46//! ## Examples
47//!
48//! ### Simple Email
49//!
50//! ```rust,no_run
51//! # #[tokio::main]
52//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
53//! use reinhardt_mail::send_mail;
54//! use reinhardt_conf::settings::EmailSettings;
55//!
56//! let mut settings = EmailSettings::default();
57//! settings.backend = "console".to_string();
58//! settings.from_email = "noreply@example.com".to_string();
59//!
60//! send_mail(
61//! &settings,
62//! "Welcome!",
63//! "Welcome to our service",
64//! vec!["user@example.com"],
65//! None,
66//! ).await?;
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! ### Email with Attachments
72//!
73//! ```rust,no_run
74//! use reinhardt_mail::{EmailMessage, Attachment};
75//!
76//! let pdf_data = b"PDF content".to_vec();
77//! let attachment = Attachment::new("report.pdf", pdf_data);
78//!
79//! let email = EmailMessage::builder()
80//! .from("reports@example.com")
81//! .to(vec!["user@example.com".to_string()])
82//! .subject("Monthly Report")
83//! .body("Please find attached your monthly report.")
84//! .attachment(attachment)
85//! .build()?;
86//! # Ok::<(), Box<dyn std::error::Error>>(())
87//! ```
88//!
89//! ### HTML Email with Inline Images
90//!
91//! ```rust,no_run
92//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
93//! use reinhardt_mail::{EmailMessage, Attachment};
94//!
95//! let logo_data = b"PNG content".to_vec();
96//! let logo = Attachment::inline("logo.png", logo_data, "logo-cid");
97//!
98//! let email = EmailMessage::builder()
99//! .from("marketing@example.com")
100//! .to(vec!["customer@example.com".to_string()])
101//! .subject("Newsletter")
102//! .body("Newsletter content")
103//! .html(r#"<html><body><img src="cid:logo-cid"/><h1>Newsletter</h1></body></html>"#)
104//! .attachment(logo)
105//! .build()?;
106//! # Ok(())
107//! # }
108//! ```
109//!
110//! ### Template-based Emails
111//!
112//! ```rust,no_run
113//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
114//! use reinhardt_mail::templates::{TemplateEmailBuilder, TemplateContext};
115//!
116//! let mut context = TemplateContext::new();
117//! context.insert("name".to_string(), "Alice".into());
118//! context.insert("order_id".to_string(), "12345".into());
119//!
120//! let email = TemplateEmailBuilder::new()
121//! .from("orders@example.com")
122//! .to(vec!["customer@example.com".to_string()])
123//! .subject_template("Order {{order_id}} Confirmation")
124//! .body_template("Hello {{name}}, your order {{order_id}} is confirmed.")
125//! .html_template("<h1>Hello {{name}}</h1><p>Order {{order_id}} confirmed.</p>")
126//! .context(context)
127//! .build()?;
128//! # Ok(())
129//! # }
130//! ```
131//!
132//! ### SMTP with TLS
133//!
134//! ```rust,no_run
135//! # #[tokio::main]
136//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
137//! use reinhardt_mail::{SmtpBackend, SmtpConfig, SmtpSecurity, EmailMessage};
138//! use std::time::Duration;
139//!
140//! let config = SmtpConfig::new("smtp.gmail.com", 587)
141//! .with_credentials("user@gmail.com".to_string(), "password".to_string())
142//! .with_security(SmtpSecurity::StartTls)
143//! .with_timeout(Duration::from_secs(30));
144//!
145//! let backend = SmtpBackend::new(config)?;
146//!
147//! let email = EmailMessage::builder()
148//! .from("sender@gmail.com")
149//! .to(vec!["recipient@example.com".to_string()])
150//! .subject("Test")
151//! .body("Test message")
152//! .build()?;
153//!
154//! email.send(&backend).await?;
155//! # Ok(())
156//! # }
157//! ```
158//!
159//! ### Bulk Sending with Connection Pool
160//!
161//! ```rust,no_run
162//! # #[tokio::main]
163//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
164//! use reinhardt_mail::pooling::{EmailPool, PoolConfig};
165//! use reinhardt_mail::{SmtpConfig, EmailMessage};
166//!
167//! let smtp_config = SmtpConfig::new("smtp.example.com", 587);
168//! let pool_config = PoolConfig::new().with_max_connections(5);
169//!
170//! let pool = EmailPool::new(smtp_config, pool_config)?;
171//!
172//! let messages = vec![
173//! EmailMessage::builder()
174//! .from("sender@example.com")
175//! .to(vec!["user1@example.com".to_string()])
176//! .subject("Newsletter")
177//! .body("Content")
178//! .build()?,
179//! // ... more messages
180//! ];
181//!
182//! let sent_count = pool.send_bulk(messages).await?;
183//! # Ok(())
184//! # }
185
186pub mod backends;
187pub mod headers;
188pub mod message;
189pub mod pooling;
190pub mod templates;
191pub mod utils;
192pub mod validation;
193
194use thiserror::Error;
195
196pub use backends::{
197 ConsoleBackend, EmailBackend, FileBackend, MemoryBackend, SmtpAuthMechanism, SmtpBackend,
198 SmtpConfig, SmtpSecurity, backend_from_settings,
199};
200pub use message::{Alternative, Attachment, EmailMessage, EmailMessageBuilder};
201pub use utils::{mail_admins, mail_managers, send_mail, send_mail_with_backend, send_mass_mail};
202pub use validation::MAX_EMAIL_LENGTH;
203
204#[derive(Debug, Error)]
205pub enum EmailError {
206 #[error("Invalid email address: {0}")]
207 InvalidAddress(String),
208
209 #[error("Missing required field: {0}")]
210 MissingField(String),
211
212 #[error("Backend error: {0}")]
213 BackendError(String),
214
215 #[error("SMTP error: {0}")]
216 SmtpError(String),
217
218 #[error("IO error: {0}")]
219 IoError(#[from] std::io::Error),
220
221 #[error("Template error: {0}")]
222 TemplateError(String),
223
224 #[error("Attachment error: {0}")]
225 AttachmentError(String),
226
227 #[error("Invalid header: {0}")]
228 InvalidHeader(String),
229
230 #[error("Header injection attempt detected: {0}")]
231 HeaderInjection(String),
232}
233
234impl EmailError {
235 /// Returns true if this is a transient error that can be safely suppressed
236 /// by fail_silently mode.
237 ///
238 /// Configuration errors, authentication failures, and security errors
239 /// are never considered transient and will always propagate even when
240 /// fail_silently is enabled.
241 pub fn is_transient(&self) -> bool {
242 matches!(self, EmailError::IoError(_) | EmailError::SmtpError(_))
243 }
244}
245
246pub type EmailResult<T> = std::result::Result<T, EmailError>;