Skip to main content

agentmail/types/
messages.rs

1use crate::util::QueryBuilder;
2use serde::{Deserialize, Serialize};
3
4use super::{Attachment, SendAttachment};
5
6/// Request body for [`Client::send_message`]. At least one recipient in `to`
7/// and one of `text`/`html` are required by the API.
8#[derive(Clone, Debug, Default, Serialize)]
9pub struct SendMessage {
10    /// Primary recipients.
11    #[serde(skip_serializing_if = "Vec::is_empty")]
12    pub to: Vec<String>,
13    /// Carbon-copy recipients.
14    #[serde(skip_serializing_if = "Vec::is_empty")]
15    pub cc: Vec<String>,
16    /// Blind-carbon-copy recipients.
17    #[serde(skip_serializing_if = "Vec::is_empty")]
18    pub bcc: Vec<String>,
19    /// Subject line.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub subject: Option<String>,
22    /// Plain-text body.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub text: Option<String>,
25    /// HTML body (send both for multipart).
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub html: Option<String>,
28    /// Labels to attach to the sent message.
29    #[serde(skip_serializing_if = "Vec::is_empty")]
30    pub labels: Vec<String>,
31    /// Reply-To addresses.
32    #[serde(skip_serializing_if = "Vec::is_empty")]
33    pub reply_to: Vec<String>,
34    /// Files to attach.
35    #[serde(skip_serializing_if = "Vec::is_empty")]
36    pub attachments: Vec<SendAttachment>,
37    /// Extra headers to set on the outgoing message.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub headers: Option<serde_json::Value>,
40}
41/// The API's acknowledgement of a send.
42#[derive(Clone, Debug, Deserialize)]
43pub struct SentMessage {
44    /// Id of the message just sent.
45    pub message_id: String,
46    /// Thread the message was filed under.
47    pub thread_id: String,
48}
49/// Request body for [`Client::reply_to_message`] and
50/// [`Client::reply_all_to_message`]. The `to` field is derived from the
51/// parent message; at least one of `text`/`html` is required by the API.
52#[derive(Clone, Debug, Default, Serialize)]
53pub struct ReplyToMessage {
54    /// Carbon-copy recipients (in addition to those on the thread).
55    #[serde(skip_serializing_if = "Vec::is_empty")]
56    pub cc: Vec<String>,
57    /// Blind-carbon-copy recipients.
58    #[serde(skip_serializing_if = "Vec::is_empty")]
59    pub bcc: Vec<String>,
60    /// Plain-text body.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub text: Option<String>,
63    /// HTML body.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub html: Option<String>,
66    /// Labels to attach to the reply.
67    #[serde(skip_serializing_if = "Vec::is_empty")]
68    pub labels: Vec<String>,
69    /// Files to attach.
70    #[serde(skip_serializing_if = "Vec::is_empty")]
71    pub attachments: Vec<SendAttachment>,
72    /// Extra headers to set on the outgoing message.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub headers: Option<serde_json::Value>,
75}
76
77/// The presigned download for a message's raw RFC 822 source, from
78/// [`Client::get_raw_message`]. Fetch the bytes with [`Client::download_raw`].
79#[derive(Clone, Debug, Deserialize)]
80pub struct RawMessage {
81    /// The message id.
82    pub message_id: String,
83    /// Size of the raw message in bytes.
84    #[serde(default)]
85    pub size: Option<u64>,
86    /// Short-lived presigned URL to download the `.eml` bytes.
87    pub download_url: String,
88    /// When `download_url` expires (RFC 3339).
89    #[serde(default)]
90    pub expires_at: Option<String>,
91}
92
93/// Request body for [`Client::batch_get_messages`].
94#[derive(Clone, Debug, Default, Serialize)]
95pub struct BatchGetMessages {
96    /// The message ids to fetch.
97    pub message_ids: Vec<String>,
98}
99
100/// Response to [`Client::batch_get_messages`].
101#[derive(Clone, Debug, Deserialize)]
102pub struct BatchGetMessagesResponse {
103    /// The number of messages returned.
104    pub count: u64,
105    /// The requested messages.
106    #[serde(default)]
107    pub messages: Vec<Message>,
108}
109
110/// Request body for [`Client::batch_update_messages`]: apply the same label
111/// changes to many messages at once.
112#[derive(Clone, Debug, Default, Serialize)]
113pub struct BatchUpdateMessages {
114    /// The message ids to update.
115    pub message_ids: Vec<String>,
116    /// Labels to add to each message.
117    #[serde(skip_serializing_if = "Vec::is_empty")]
118    pub add_labels: Vec<String>,
119    /// Labels to remove from each message.
120    #[serde(skip_serializing_if = "Vec::is_empty")]
121    pub remove_labels: Vec<String>,
122}
123
124/// Response to [`Client::batch_update_messages`].
125#[derive(Clone, Debug, Deserialize)]
126pub struct BatchUpdateMessagesResponse {
127    /// The number of messages updated.
128    pub count: u64,
129    /// The per-message results.
130    #[serde(default)]
131    pub updates: Vec<UpdatedMessage>,
132}
133/// Request body for [`Client::update_message`].
134#[derive(Clone, Debug, Default, Serialize)]
135pub struct UpdateMessage {
136    /// Labels to add.
137    #[serde(skip_serializing_if = "Vec::is_empty")]
138    pub add_labels: Vec<String>,
139    /// Labels to remove.
140    #[serde(skip_serializing_if = "Vec::is_empty")]
141    pub remove_labels: Vec<String>,
142}
143/// The API's response to [`Client::update_message`]: the message id and its
144/// labels after the update.
145#[derive(Clone, Debug, Deserialize)]
146pub struct UpdatedMessage {
147    /// Id of the updated message.
148    pub message_id: String,
149    /// The message's labels after the update.
150    #[serde(default)]
151    pub labels: Vec<String>,
152}
153/// A message as the API returns it. List items are a subset of the full
154/// get-message shape; every non-id field defaults so both parse.
155#[derive(Clone, Debug, Deserialize)]
156pub struct Message {
157    /// Unique id within the inbox.
158    pub message_id: String,
159    /// Inbox the message belongs to.
160    #[serde(default)]
161    pub inbox_id: Option<String>,
162    /// Conversation thread id.
163    #[serde(default)]
164    pub thread_id: Option<String>,
165    /// Sender address.
166    #[serde(default)]
167    pub from: Option<String>,
168    /// Recipient addresses.
169    #[serde(default)]
170    pub to: Vec<String>,
171    /// Subject line.
172    #[serde(default)]
173    pub subject: Option<String>,
174    /// Short plain-text excerpt (list responses).
175    #[serde(default)]
176    pub preview: Option<String>,
177    /// Full plain-text body (get responses).
178    #[serde(default)]
179    pub text: Option<String>,
180    /// Full HTML body (get responses).
181    #[serde(default)]
182    pub html: Option<String>,
183    /// Labels on the message (e.g. `received`, `unread`).
184    #[serde(default)]
185    pub labels: Vec<String>,
186    /// RFC 3339 send/receive timestamp.
187    #[serde(default)]
188    pub timestamp: Option<String>,
189    /// Attachments on the message (present in get-message responses).
190    #[serde(default)]
191    pub attachments: Vec<Attachment>,
192}
193/// One page of messages from [`Client::list_messages_page`].
194#[derive(Clone, Debug, Deserialize)]
195pub struct MessageList {
196    /// Total messages in the inbox (not just this page).
197    pub count: u64,
198    /// This page of messages.
199    #[serde(default)]
200    pub messages: Vec<Message>,
201    /// Cursor for the next page; `None` on the last page.
202    #[serde(default)]
203    pub next_page_token: Option<String>,
204}
205/// Filters for [`Client::list_messages_filtered`] and
206/// [`Client::search_messages_page`]. Pagination fields (`limit`,
207/// `page_token`) live here because they share the same query-parameter
208/// namespace as the filter fields.
209#[derive(Clone, Debug, Default)]
210pub struct MessageListFilters {
211    /// Maximum items per page.
212    pub limit: Option<u32>,
213    /// Cursor from a previous response's `next_page_token`.
214    pub page_token: Option<String>,
215    /// Filter by labels.
216    pub labels: Vec<String>,
217    /// Only messages before this timestamp (RFC 3339).
218    pub before: Option<String>,
219    /// Only messages after this timestamp (RFC 3339).
220    pub after: Option<String>,
221    /// Return oldest first instead of newest first.
222    pub ascending: Option<bool>,
223    /// Include spam messages.
224    pub include_spam: Option<bool>,
225    /// Include blocked messages.
226    pub include_blocked: Option<bool>,
227    /// Include unauthenticated messages.
228    pub include_unauthenticated: Option<bool>,
229    /// Include trashed messages.
230    pub include_trash: Option<bool>,
231    /// Filter by sender substring (repeatable).
232    pub from: Vec<String>,
233    /// Filter by recipient substring -- matches to, cc, or bcc (repeatable).
234    pub to: Vec<String>,
235    /// Filter by subject substring (repeatable).
236    pub subject: Vec<String>,
237}
238impl MessageListFilters {
239    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
240        QueryBuilder::new()
241            .opt("limit", self.limit.as_ref())
242            .opt("page_token", self.page_token.as_ref())
243            .many("labels", &self.labels)
244            .opt("before", self.before.as_ref())
245            .opt("after", self.after.as_ref())
246            .opt("ascending", self.ascending.as_ref())
247            .opt("include_spam", self.include_spam.as_ref())
248            .opt("include_blocked", self.include_blocked.as_ref())
249            .opt(
250                "include_unauthenticated",
251                self.include_unauthenticated.as_ref(),
252            )
253            .opt("include_trash", self.include_trash.as_ref())
254            .many("from", &self.from)
255            .many("to", &self.to)
256            .many("subject", &self.subject)
257            .build()
258    }
259}