Skip to main content

wangamail_rs/
graph.rs

1//! Types for the Microsoft Graph [sendMail](https://learn.microsoft.com/en-us/graph/api/user-sendmail) API.
2//!
3//! These types map to the JSON payloads used by `POST /users/{id}/sendMail`.
4
5use serde::{Deserialize, Serialize};
6
7/// Body content type for the message.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "PascalCase")]
10pub enum BodyType {
11    /// Plain text body.
12    Text,
13    /// HTML body.
14    HTML,
15}
16
17/// Email address (address + optional display name).
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EmailAddress {
20    /// Email address (e.g. `user@example.com`).
21    pub address: String,
22    /// Optional display name.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub name: Option<String>,
25}
26
27impl EmailAddress {
28    /// Create an address with no display name.
29    pub fn new(address: impl Into<String>) -> Self {
30        Self {
31            address: address.into(),
32            name: None,
33        }
34    }
35    /// Set the display name and return `self`.
36    pub fn with_name(mut self, name: impl Into<String>) -> Self {
37        self.name = Some(name.into());
38        self
39    }
40}
41
42/// A single recipient (to, cc, or bcc).
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Recipient {
45    /// The recipient’s email address (and optional name).
46    pub email_address: EmailAddress,
47}
48
49impl Recipient {
50    /// Create a recipient by email address only.
51    pub fn new(address: impl Into<String>) -> Self {
52        Self {
53            email_address: EmailAddress::new(address),
54        }
55    }
56    /// Create a recipient with an optional display name.
57    pub fn with_name(address: impl Into<String>, name: impl Into<String>) -> Self {
58        Self {
59            email_address: EmailAddress::new(address).with_name(name),
60        }
61    }
62}
63
64/// Message body (content type + raw content).
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct MessageBody {
67    /// Whether the content is plain text or HTML.
68    #[serde(rename = "contentType")]
69    pub content_type: BodyType,
70    /// Raw body content (plain text or HTML string).
71    pub content: String,
72}
73
74/// File attachment (Graph fileAttachment; content as base64).
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct FileAttachment {
77    /// OData type; use `#microsoft.graph.fileAttachment`.
78    #[serde(rename = "@odata.type")]
79    pub odata_type: String,
80    /// File name (e.g. `document.pdf`).
81    pub name: String,
82    /// MIME type (e.g. `application/pdf`).
83    #[serde(rename = "contentType")]
84    pub content_type: String,
85    /// File contents, base64-encoded.
86    #[serde(rename = "contentBytes")]
87    pub content_bytes: String,
88}
89
90impl FileAttachment {
91    /// Create a file attachment from a base64-encoded payload.
92    pub fn new(
93        name: impl Into<String>,
94        content_type: impl Into<String>,
95        content_bytes_base64: impl Into<String>,
96    ) -> Self {
97        Self {
98            odata_type: "#microsoft.graph.fileAttachment".to_string(),
99            name: name.into(),
100            content_type: content_type.into(),
101            content_bytes: content_bytes_base64.into(),
102        }
103    }
104}
105
106/// Attachment variant for Graph sendMail (file attachments).
107#[allow(dead_code)]
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[serde(tag = "@odata.type", rename_all = "camelCase")]
110pub enum Attachment {
111    /// File attachment (use [`FileAttachment`] for construction).
112    #[serde(rename = "#microsoft.graph.fileAttachment")]
113    File(FileAttachment),
114}
115
116/// Message payload for sendMail.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Message {
119    /// Subject line.
120    pub subject: String,
121    /// Body (text or HTML).
122    pub body: MessageBody,
123    /// To recipients.
124    #[serde(rename = "toRecipients")]
125    pub to_recipients: Vec<Recipient>,
126    /// CC recipients (optional; omitted if empty).
127    #[serde(rename = "ccRecipients", skip_serializing_if = "Vec::is_empty")]
128    pub cc_recipients: Vec<Recipient>,
129    /// BCC recipients (optional; omitted if empty).
130    #[serde(rename = "bccRecipients", skip_serializing_if = "Vec::is_empty")]
131    pub bcc_recipients: Vec<Recipient>,
132    /// Optional file attachments.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub attachments: Option<Vec<FileAttachment>>,
135}
136
137impl Default for Message {
138    fn default() -> Self {
139        Self {
140            subject: String::new(),
141            body: MessageBody {
142                content_type: BodyType::Text,
143                content: String::new(),
144            },
145            to_recipients: vec![],
146            cc_recipients: vec![],
147            bcc_recipients: vec![],
148            attachments: None,
149        }
150    }
151}
152
153/// Request body for `POST /users/{id}/sendMail`.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct SendMailRequest {
156    /// The message to send.
157    pub message: Message,
158    /// Whether to save a copy in the sender’s Sent Items. Default when using [`SendMailRequest::new`] is `true`.
159    #[serde(rename = "saveToSentItems", skip_serializing_if = "Option::is_none")]
160    pub save_to_sent_items: Option<bool>,
161}
162
163impl SendMailRequest {
164    /// Create a send request for the given message. Sent Items saving defaults to `true`.
165    pub fn new(message: Message) -> Self {
166        Self {
167            message,
168            save_to_sent_items: Some(true),
169        }
170    }
171    /// Set whether to save a copy in Sent Items.
172    pub fn save_to_sent_items(mut self, save: bool) -> Self {
173        self.save_to_sent_items = Some(save);
174        self
175    }
176}