Skip to main content

postmark/
emails.rs

1use std::collections::HashMap;
2
3use reqwest::Method;
4use serde::{Deserialize, Serialize};
5
6use crate::{ApiError, Client, SendRequestInput};
7
8#[derive(Clone, Debug, Default, Deserialize, Serialize)]
9#[serde(rename_all = "PascalCase")]
10pub struct Email {
11    /// From: The sender email address. Must have a registered and confirmed Sender Signature.
12    pub from: String,
13
14    /// To: Recipient email address. Multiple addresses are comma separated. Max 50.
15    pub to: String,
16
17    /// Cc recipient email address. Multiple addresses are comma separated. Max 50.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub cc: Option<String>,
20
21    /// Bcc recipient email address. Multiple addresses are comma separated. Max 50.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub bcc: Option<String>,
24
25    /// Email subject
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub subject: Option<String>,
28
29    /// The body of the message
30    #[serde(flatten)]
31    pub body: Body,
32
33    /// Email tag that allows you to categorize outgoing emails and get detailed statistics.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub tag: Option<String>,
36
37    /// Reply To override email address. Defaults to the Reply To set in the sender signature.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub reply_to: Option<String>,
40
41    /// List of custom headers to include.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub headers: Option<Vec<Header>>,
44
45    /// Activate open tracking for this email.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub track_opens: Option<bool>,
48
49    /// Activate link tracking for links in the HTML or Text bodies of this email.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub track_links: Option<TrackLink>,
52
53    /// List of attachments
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub attachments: Option<Vec<Attachment>>,
56
57    /// Custom metadata key/value pairs.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub metadata: Option<HashMap<String, String>>,
60
61    /// Set message stream ID that's used for sending. If not provided, message will default to the "outbound" transactional stream.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub message_stream: Option<String>,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[serde(untagged)]
68pub enum Body {
69    Text {
70        #[serde(rename = "TextBody")]
71        text: String,
72    },
73    Html {
74        #[serde(rename = "HtmlBody")]
75        html: String,
76    },
77    HtmlAndText {
78        #[serde(rename = "HtmlBody")]
79        html: String,
80        #[serde(rename = "TextBody")]
81        text: String,
82    },
83}
84
85impl Default for Body {
86    fn default() -> Self {
87        Body::Text {
88            text: "".into(),
89        }
90    }
91}
92
93impl Body {
94    /// Constructor to create a text-only [`Body`] enum
95    pub fn text(text: String) -> Self {
96        Body::Text {
97            text,
98        }
99    }
100    /// Constructor to create a html-only [`Body`] enum
101    pub fn html(html: String) -> Self {
102        Body::Html {
103            html,
104        }
105    }
106    /// Constructor to create a text and html [`Body`] enum
107    pub fn html_and_text(html: String, text: String) -> Self {
108        Body::HtmlAndText {
109            html,
110            text,
111        }
112    }
113}
114
115/// A custom header to include in an email.
116#[derive(Clone, Debug, Deserialize, Serialize)]
117#[serde(rename_all = "PascalCase")]
118pub struct Header {
119    pub name: String,
120    pub value: String,
121}
122
123/// An attachment for emails.
124#[derive(Clone, Debug, Default, Deserialize, Serialize)]
125#[serde(rename_all = "PascalCase")]
126pub struct Attachment {
127    pub name: String,
128    pub content: String,
129    pub content_type: String,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub content_id: Option<String>,
132}
133
134/// Whether to activate link tracking for links in the HTML or Text bodies of the emails.
135#[derive(Clone, Debug, Deserialize, Serialize)]
136pub enum TrackLink {
137    None,
138    HtmlAndText,
139    HtmlOnly,
140    TextOnly,
141}
142
143impl Default for TrackLink {
144    fn default() -> Self {
145        Self::None
146    }
147}
148
149#[derive(Clone, Debug, Default, Deserialize, Serialize)]
150#[serde(rename_all = "PascalCase")]
151pub struct SendEmailResponse {
152    pub to: Option<String>,
153    pub submitted_at: Option<String>,
154    #[serde(rename = "MessageID")]
155    pub message_id: Option<String>,
156    pub error_code: i64,
157    pub message: String,
158}
159
160impl Client {
161    pub async fn send_email(&self, server_token: String, email: Email) -> Result<SendEmailResponse, ApiError> {
162        return self
163            .send_request(SendRequestInput {
164                method: Method::POST,
165                url: "/email".to_string(),
166                body: email,
167                server_token: Some(server_token),
168            })
169            .await;
170    }
171}