1use std::fmt;
10
11use lettre::message::header::ContentType;
12use lettre::message::{Attachment, Mailbox, Message, MessageBuilder, MultiPart, SinglePart};
13
14use crate::mail::error::EmailError;
15
16#[derive(Clone)]
19pub struct Email {
20 builder: MessageBuilder,
21}
22
23impl Email {
24 #[must_use]
26 pub fn builder() -> Self {
27 Self {
28 builder: Message::builder(),
29 }
30 }
31
32 #[must_use]
34 pub fn from_builder(builder: MessageBuilder) -> Self {
35 Self { builder }
36 }
37
38 #[must_use]
40 pub fn from(mut self, mailbox: Mailbox) -> Self {
41 self.builder = self.builder.from(mailbox);
42 self
43 }
44
45 #[must_use]
47 pub fn reply_to(mut self, mailbox: Mailbox) -> Self {
48 self.builder = self.builder.reply_to(mailbox);
49 self
50 }
51
52 #[must_use]
54 pub fn to(mut self, mailbox: Mailbox) -> Self {
55 self.builder = self.builder.to(mailbox);
56 self
57 }
58
59 #[must_use]
61 pub fn cc(mut self, mailbox: Mailbox) -> Self {
62 self.builder = self.builder.cc(mailbox);
63 self
64 }
65
66 #[must_use]
68 pub fn bcc(mut self, mailbox: Mailbox) -> Self {
69 self.builder = self.builder.bcc(mailbox);
70 self
71 }
72
73 #[must_use]
75 pub fn subject(mut self, subject: impl Into<String>) -> Self {
76 self.builder = self.builder.subject(subject);
77 self
78 }
79
80 pub fn plain(self, body: impl Into<String>) -> Result<Message, EmailError> {
86 let body: String = body.into();
87 self.builder
88 .header(ContentType::TEXT_PLAIN)
89 .body(body)
90 .map_err(EmailError::build)
91 }
92
93 pub fn html(self, body: impl Into<String>) -> Result<Message, EmailError> {
99 let html_part = SinglePart::builder()
100 .header(ContentType::TEXT_HTML)
101 .body(body.into());
102 self.builder
103 .singlepart(html_part)
104 .map_err(EmailError::build)
105 }
106
107 pub fn alternative(
114 self,
115 plain: impl Into<String>,
116 html: impl Into<String>,
117 ) -> Result<Message, EmailError> {
118 let multipart = MultiPart::alternative_plain_html(plain.into(), html.into());
119 self.builder.multipart(multipart).map_err(EmailError::build)
120 }
121
122 pub fn mixed(
129 self,
130 body: MultiPart,
131 attachments: Vec<EmailAttachment>,
132 ) -> Result<Message, EmailError> {
133 let mut mixed = MultiPart::mixed().multipart(body);
134 for attachment in attachments {
135 mixed = mixed.singlepart(attachment.into_lettre());
136 }
137 self.builder.multipart(mixed).map_err(EmailError::build)
138 }
139
140 pub fn plain_with_attachments(
147 self,
148 body: impl Into<String>,
149 attachments: Vec<EmailAttachment>,
150 ) -> Result<Message, EmailError> {
151 let body = MultiPart::alternative_plain_html(body.into(), String::new());
152 self.mixed(body, attachments)
153 }
154
155 pub fn alternative_with_attachments(
162 self,
163 plain: impl Into<String>,
164 html: impl Into<String>,
165 attachments: Vec<EmailAttachment>,
166 ) -> Result<Message, EmailError> {
167 let body = MultiPart::alternative_plain_html(plain.into(), html.into());
168 self.mixed(body, attachments)
169 }
170
171 #[must_use]
174 pub fn into_builder(self) -> MessageBuilder {
175 self.builder
176 }
177}
178
179pub struct EmailAttachment {
181 filename: String,
182 body: Vec<u8>,
183 content_type: ContentType,
184}
185
186impl EmailAttachment {
187 pub fn new(
194 filename: impl Into<String>,
195 body: Vec<u8>,
196 content_type: &str,
197 ) -> Result<Self, EmailError> {
198 let content_type = ContentType::parse(content_type).map_err(EmailError::content_type)?;
199 Ok(Self {
200 filename: filename.into(),
201 body,
202 content_type,
203 })
204 }
205
206 pub(crate) fn into_lettre(self) -> SinglePart {
207 Attachment::new(self.filename).body(self.body, self.content_type)
208 }
209}
210
211impl fmt::Debug for EmailAttachment {
212 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
213 formatter
214 .debug_struct("EmailAttachment")
215 .field("filename", &self.filename)
216 .field("content_type", &self.content_type)
217 .field("body_len", &self.body.len())
218 .finish_non_exhaustive()
219 }
220}