acton_htmx/email/backend/
console.rs1use async_trait::async_trait;
7use tracing::{debug, info};
8
9use crate::email::{Email, EmailError, EmailSender};
10
11#[derive(Debug, Clone, Default)]
35pub struct ConsoleBackend {
36 verbose: bool,
38}
39
40impl ConsoleBackend {
41 #[must_use]
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 #[must_use]
49 pub const fn verbose() -> Self {
50 Self { verbose: true }
51 }
52}
53
54#[async_trait]
55impl EmailSender for ConsoleBackend {
56 async fn send(&self, email: Email) -> Result<(), EmailError> {
57 email.validate()?;
59
60 let from = email.from.as_ref().ok_or(EmailError::NoSender)?;
61 let subject = email.subject.as_ref().ok_or(EmailError::NoSubject)?;
62
63 info!(
65 from = %from,
66 to = ?email.to,
67 cc = ?email.cc,
68 bcc = ?email.bcc,
69 subject = %subject,
70 "Console email sent"
71 );
72
73 if self.verbose {
74 debug!(
75 reply_to = ?email.reply_to,
76 has_html = email.html.is_some(),
77 has_text = email.text.is_some(),
78 headers = ?email.headers,
79 "Email details"
80 );
81
82 if let Some(text) = &email.text {
83 debug!(text = %text, "Email text content");
84 }
85
86 if let Some(html) = &email.html {
87 debug!(html = %html, "Email HTML content");
88 }
89 }
90
91 println!("\n╭─────────────────────────────────────────────────────╮");
93 println!("│ 📧 Console Email │");
94 println!("├─────────────────────────────────────────────────────┤");
95 println!("│ From: {from:<43} │");
96 println!("│ To: {:<43} │", email.to.join(", "));
97 if !email.cc.is_empty() {
98 println!("│ CC: {:<43} │", email.cc.join(", "));
99 }
100 if !email.bcc.is_empty() {
101 println!("│ BCC: {:<43} │", email.bcc.join(", "));
102 }
103 if let Some(reply_to) = &email.reply_to {
104 println!("│ Reply-To: {reply_to:<42} │");
105 }
106 println!("│ Subject: {subject:<43} │");
107 println!("├─────────────────────────────────────────────────────┤");
108
109 if let Some(text) = &email.text {
110 println!("│ Plain Text Content: │");
111 println!("├─────────────────────────────────────────────────────┤");
112 for line in text.lines() {
113 let truncated = if line.len() > 51 {
114 format!("{}...", &line[..48])
115 } else {
116 line.to_string()
117 };
118 println!("│ {truncated:<51} │");
119 }
120 println!("├─────────────────────────────────────────────────────┤");
121 }
122
123 if let Some(html) = &email.html {
124 println!("│ HTML Content: │");
125 println!("├─────────────────────────────────────────────────────┤");
126 for line in html.lines().take(5) {
127 let truncated = if line.len() > 51 {
128 format!("{}...", &line[..48])
129 } else {
130 line.to_string()
131 };
132 println!("│ {truncated:<51} │");
133 }
134 if html.lines().count() > 5 {
135 println!("│ ... (truncated) │");
136 }
137 println!("├─────────────────────────────────────────────────────┤");
138 }
139
140 println!("╰─────────────────────────────────────────────────────╯\n");
141
142 Ok(())
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[tokio::test]
151 async fn test_console_backend_send() {
152 let backend = ConsoleBackend::new();
153
154 let email = Email::new()
155 .to("user@example.com")
156 .from("noreply@myapp.com")
157 .subject("Test Email")
158 .text("This is a test email");
159
160 let result = backend.send(email).await;
161 assert!(result.is_ok());
162 }
163
164 #[tokio::test]
165 async fn test_console_backend_verbose() {
166 let backend = ConsoleBackend::verbose();
167
168 let email = Email::new()
169 .to("user@example.com")
170 .from("noreply@myapp.com")
171 .subject("Test Email")
172 .text("This is plain text")
173 .html("<h1>This is HTML</h1>");
174
175 let result = backend.send(email).await;
176 assert!(result.is_ok());
177 }
178
179 #[tokio::test]
180 async fn test_console_backend_with_cc_and_bcc() {
181 let backend = ConsoleBackend::new();
182
183 let email = Email::new()
184 .to("user@example.com")
185 .cc("cc@example.com")
186 .bcc("bcc@example.com")
187 .from("noreply@myapp.com")
188 .subject("Test Email")
189 .text("Test content");
190
191 let result = backend.send(email).await;
192 assert!(result.is_ok());
193 }
194}