1 use serde::{Deserialize, Serialize};
2 use serde_repr::{Deserialize_repr, Serialize_repr};
3
4 /// Delivery status of a transactional email, mirroring the server-side
5 /// integer-backed enum. Deserialized with `serde_repr` from the plain integer
6 /// the API returns.
7 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
8 #[repr(u8)]
9 pub enum EmailStatus {
10 Queued = 0,
11 Sent = 1,
12 Delivered = 2,
13 Bounced = 3,
14 Complained = 4,
15 Failed = 5,
16 }
17
18 /// One attachment on an outbound email. `content_base64` is the file content
19 /// encoded as standard base64; the server reassembles it into the MIME message.
20 #[derive(Debug, Clone, Serialize, Deserialize)]
21 pub struct Attachment {
22 pub filename: String,
23 pub content_base64: String,
24 }
25
26 /// An outbound transactional email.
27 ///
28 /// Build one with [`SendEmail::new`] and the builder helpers, then hand it to
29 /// [`crate::EmailClient::send`]. At least one of `html` or `text` is required;
30 /// `from` must be on the API key's verified domain.
31 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
32 #[serde(default)]
33 pub struct SendEmail {
34 pub from: String,
35 pub to: Vec<String>,
36 #[serde(skip_serializing_if = "Vec::is_empty")]
37 pub cc: Vec<String>,
38 #[serde(skip_serializing_if = "Vec::is_empty")]
39 pub bcc: Vec<String>,
40 #[serde(skip_serializing_if = "Vec::is_empty")]
41 pub reply_to: Vec<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub subject: Option<String>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub html: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub text: Option<String>,
48 #[serde(skip_serializing_if = "Vec::is_empty")]
49 pub attachments: Vec<Attachment>,
50 #[serde(skip_serializing_if = "std::collections::HashMap::is_empty")]
51 pub headers: std::collections::HashMap<String, String>,
52 #[serde(skip_serializing_if = "std::collections::HashMap::is_empty")]
53 pub tags: std::collections::HashMap<String, String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub idempotency_key: Option<String>,
56 }
57
58 impl SendEmail {
59 /// Start a message from a sender address and one or more recipients.
60 pub fn new(from: impl Into<String>, to: impl IntoIterator<Item = impl Into<String>>) -> Self {
61 SendEmail {
62 from: from.into(),
63 to: to.into_iter().map(Into::into).collect(),
64 ..Default::default()
65 }
66 }
67
68 pub fn subject(mut self, subject: impl Into<String>) -> Self {
69 self.subject = Some(subject.into());
70 self
71 }
72
73 pub fn html(mut self, html: impl Into<String>) -> Self {
74 self.html = Some(html.into());
75 self
76 }
77
78 pub fn text(mut self, text: impl Into<String>) -> Self {
79 self.text = Some(text.into());
80 self
81 }
82
83 pub fn cc(mut self, cc: impl IntoIterator<Item = impl Into<String>>) -> Self {
84 self.cc = cc.into_iter().map(Into::into).collect();
85 self
86 }
87
88 pub fn bcc(mut self, bcc: impl IntoIterator<Item = impl Into<String>>) -> Self {
89 self.bcc = bcc.into_iter().map(Into::into).collect();
90 self
91 }
92
93 pub fn reply_to(mut self, reply_to: impl IntoIterator<Item = impl Into<String>>) -> Self {
94 self.reply_to = reply_to.into_iter().map(Into::into).collect();
95 self
96 }
97
98 pub fn attachment(mut self, attachment: Attachment) -> Self {
99 self.attachments.push(attachment);
100 self
101 }
102
103 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
104 self.headers.insert(key.into(), value.into());
105 self
106 }
107
108 pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
109 self.tags.insert(key.into(), value.into());
110 self
111 }
112
113 /// Make a retried send safe: the same key returns the original message
114 /// instead of sending again.
115 pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
116 self.idempotency_key = Some(key.into());
117 self
118 }
119 }
120
121 /// One delivery event in a message's timeline (delivered, bounced, opened, …).
122 /// Present on [`EmailMessage`] only when fetched via
123 /// [`crate::EmailClient::get_message`].
124 #[derive(Debug, Clone, Serialize, Deserialize)]
125 pub struct EmailEvent {
126 pub id: String,
127 pub event_type: u8,
128 #[serde(default)]
129 pub payload: serde_json::Value,
130 pub occurred_at: String,
131 }
132
133 /// A transactional email as returned by the API: the send result and, when
134 /// fetched by id, its delivery status and event timeline.
135 #[derive(Debug, Clone, Serialize, Deserialize)]
136 pub struct EmailMessage {
137 pub id: String,
138 pub status: EmailStatus,
139 pub provider_message_id: Option<String>,
140 pub idempotency_key: Option<String>,
141 pub from_email: String,
142 #[serde(default)]
143 pub to_emails: Vec<String>,
144 pub subject: Option<String>,
145 pub sent_at: Option<String>,
146 pub created_at: Option<String>,
147 /// Populated by `get_message`; empty for the bare send response.
148 #[serde(default)]
149 pub events: Vec<EmailEvent>,
150 }