postmark_client/
types.rs

1use base64::prelude::*;
2use serde::{Deserialize, Serialize, Serializer};
3use std::collections::HashMap;
4
5/// as_base64 allows for binary data to be encoded as a base64 string
6fn as_base64<T, S>(data: &T, serializer: S) -> Result<S::Ok, S::Error>
7where
8    T: AsRef<[u8]>,
9    S: Serializer,
10{
11    serializer.serialize_str(&BASE64_STANDARD.encode(data.as_ref()))
12}
13
14#[derive(Serialize, Debug)]
15pub enum TrackLinks {
16    None,
17    HtmlTAndText,
18    HtmlOnly,
19    TextOnly,
20}
21
22#[derive(Serialize, Debug)]
23#[serde(rename_all = "PascalCase")]
24pub struct EmailHeader {
25    pub name: String,
26    pub value: String,
27}
28
29#[derive(Serialize, Debug)]
30#[serde(rename_all = "PascalCase")]
31pub struct Attachment {
32    pub name: String,
33    #[serde(serialize_with = "as_base64")]
34    pub content: Vec<u8>,
35    pub content_type: String,
36}
37
38#[derive(Serialize, Debug)]
39#[serde(untagged)]
40pub enum EmailBody {
41    #[serde(rename = "TextBody")]
42    Text(String),
43    #[serde(rename = "HtmlBody")]
44    Html(String),
45    HtmlAndText {
46        #[serde(rename = "TextBody")]
47        text: String,
48        #[serde(rename = "HtmlBody")]
49        html: String,
50    },
51}
52
53#[derive(Serialize, Debug)]
54#[serde(rename_all = "PascalCase")]
55pub struct Email {
56    pub from: String,
57    pub to: String,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub cc: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub bcc: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub subject: Option<String>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub tag: Option<String>,
66    #[serde(flatten)]
67    pub body: EmailBody,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub reply_to: Option<String>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub headers: Option<Vec<EmailHeader>>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub track_opens: Option<bool>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub track_links: Option<TrackLinks>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub metadata: Option<HashMap<String, String>>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub attachments: Option<Vec<Attachment>>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub message_stream: Option<String>,
82}
83
84impl Email {
85    pub fn new(from: String, to: String, body: EmailBody) -> Email {
86        Email {
87            from,
88            to,
89            cc: None,
90            bcc: None,
91            subject: None,
92            tag: None,
93            body,
94            reply_to: None,
95            headers: None,
96            track_opens: None,
97            track_links: None,
98            metadata: None,
99            attachments: None,
100            message_stream: None,
101        }
102    }
103}
104
105#[derive(Deserialize, Debug)]
106#[serde(rename_all = "PascalCase")]
107pub struct EmailResponse {
108    pub to: String,
109    pub submitted_at: String,
110    #[serde(rename = "MessageID")]
111    pub message_id: String,
112    pub error_code: i32,
113    pub message: String,
114}
115
116#[derive(Serialize, Debug)]
117pub enum TemplateToUse {
118    #[serde(rename = "TemplateId")]
119    Id(String),
120    #[serde(rename = "TemplateAlias")]
121    Alias(String),
122}
123
124#[derive(Serialize, Debug)]
125#[serde(rename_all = "PascalCase")]
126pub struct TemplatedEmail<M>
127where
128    M: Serialize,
129{
130    #[serde(flatten)]
131    pub template: TemplateToUse,
132    pub template_model: M,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub inline_css: Option<bool>,
135    pub from: String,
136    pub to: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub cc: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub bcc: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub tag: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub reply_to: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub headers: Option<Vec<EmailHeader>>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub track_opens: Option<bool>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub track_links: Option<TrackLinks>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub metadata: Option<HashMap<String, String>>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub attachments: Option<Vec<Attachment>>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub message_stream: Option<String>,
157}
158
159impl<M> TemplatedEmail<M>
160where
161    M: Serialize,
162{
163    pub fn new(from: String, to: String, template: TemplateToUse, model: M) -> TemplatedEmail<M> {
164        TemplatedEmail {
165            template,
166            template_model: model,
167            inline_css: None,
168            from,
169            to,
170            cc: None,
171            bcc: None,
172            tag: None,
173            reply_to: None,
174            headers: None,
175            track_opens: None,
176            track_links: None,
177            metadata: None,
178            attachments: None,
179            message_stream: None,
180        }
181    }
182}
183
184/// Represents an API error from Postmark.
185///
186/// <https://postmarkapp.com/developer/api/overview#error-codes>
187#[derive(Debug, Deserialize)]
188#[serde(rename_all = "PascalCase")]
189pub struct APIError {
190    pub error_code: String,
191    pub message: String,
192}