lettre/message/
attachment.rs

1use crate::message::{
2    header::{self, ContentType},
3    IntoBody, SinglePart,
4};
5
6/// `SinglePart` builder for attachments
7///
8/// Allows building attachment parts easily.
9#[derive(Clone)]
10pub struct Attachment {
11    disposition: Disposition,
12}
13
14#[derive(Clone)]
15enum Disposition {
16    /// File name
17    Attached(String),
18    /// Content id
19    Inline(String),
20}
21
22impl Attachment {
23    /// Create a new attachment
24    ///
25    /// This attachment will be displayed as a normal attachment,
26    /// with the chosen `filename` appearing as the file name.
27    ///
28    /// ```rust
29    /// # use std::error::Error;
30    /// use std::fs;
31    ///
32    /// use lettre::message::{header::ContentType, Attachment};
33    ///
34    /// # fn main() -> Result<(), Box<dyn Error>> {
35    /// let filename = String::from("invoice.pdf");
36    /// # if false {
37    /// let filebody = fs::read("invoice.pdf")?;
38    /// # }
39    /// # let filebody = fs::read("docs/lettre.png")?;
40    /// let content_type = ContentType::parse("application/pdf").unwrap();
41    /// let attachment = Attachment::new(filename).body(filebody, content_type);
42    ///
43    /// // The document `attachment` will show up as a normal attachment.
44    /// # Ok(())
45    /// # }
46    /// ```
47    pub fn new(filename: String) -> Self {
48        Attachment {
49            disposition: Disposition::Attached(filename),
50        }
51    }
52
53    /// Create a new inline attachment
54    ///
55    /// This attachment should be displayed inline into the message
56    /// body:
57    ///
58    /// ```html
59    /// <img src="cid:123">
60    /// ```
61    ///
62    ///
63    /// ```rust
64    /// # use std::error::Error;
65    /// use std::fs;
66    ///
67    /// use lettre::message::{header::ContentType, Attachment};
68    ///
69    /// # fn main() -> Result<(), Box<dyn Error>> {
70    /// let content_id = String::from("123");
71    /// # if false {
72    /// let filebody = fs::read("image.jpg")?;
73    /// # }
74    /// # let filebody = fs::read("docs/lettre.png")?;
75    /// let content_type = ContentType::parse("image/jpeg").unwrap();
76    /// let attachment = Attachment::new_inline(content_id).body(filebody, content_type);
77    ///
78    /// // The image `attachment` will display inline into the email.
79    /// # Ok(())
80    /// # }
81    /// ```
82    pub fn new_inline(content_id: String) -> Self {
83        Attachment {
84            disposition: Disposition::Inline(content_id),
85        }
86    }
87
88    /// Build the attachment into a [`SinglePart`] which can then be used to build the rest of the email
89    ///
90    /// Look at the [Complex MIME body example](crate::message#complex-mime-body)
91    /// to see how [`SinglePart`] can be put into the email.
92    pub fn body<T: IntoBody>(self, content: T, content_type: ContentType) -> SinglePart {
93        let mut builder = SinglePart::builder();
94        builder = match self.disposition {
95            Disposition::Attached(filename) => {
96                builder.header(header::ContentDisposition::attachment(&filename))
97            }
98            Disposition::Inline(content_id) => builder
99                .header(header::ContentId::from(format!("<{content_id}>")))
100                .header(header::ContentDisposition::inline()),
101        };
102        builder = builder.header(content_type);
103        builder.body(content)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use crate::message::header::ContentType;
110
111    #[test]
112    fn attachment() {
113        let part = super::Attachment::new(String::from("test.txt")).body(
114            String::from("Hello world!"),
115            ContentType::parse("text/plain").unwrap(),
116        );
117        assert_eq!(
118            &String::from_utf8_lossy(&part.formatted()),
119            concat!(
120                "Content-Disposition: attachment; filename=\"test.txt\"\r\n",
121                "Content-Type: text/plain\r\n",
122                "Content-Transfer-Encoding: 7bit\r\n\r\n",
123                "Hello world!\r\n",
124            )
125        );
126    }
127
128    #[test]
129    fn attachment_inline() {
130        let part = super::Attachment::new_inline(String::from("id")).body(
131            String::from("Hello world!"),
132            ContentType::parse("text/plain").unwrap(),
133        );
134        assert_eq!(
135            &String::from_utf8_lossy(&part.formatted()),
136            concat!(
137                "Content-ID: <id>\r\n",
138                "Content-Disposition: inline\r\n",
139                "Content-Type: text/plain\r\n",
140                "Content-Transfer-Encoding: 7bit\r\n\r\n",
141                "Hello world!\r\n"
142            )
143        );
144    }
145}