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 `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    /// Ask the API to make open tracking available for this message.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub track_opens: Option<bool>,
43}
44/// The API's acknowledgement of a send.
45#[derive(Clone, Debug, Deserialize)]
46pub struct SentMessage {
47    /// Id of the message just sent.
48    pub message_id: String,
49    /// Thread the message was filed under.
50    pub thread_id: String,
51}
52/// Request body for `reply_to_message` and
53/// `reply_all_to_message`. `to` overrides the derived recipients when
54/// non-empty; at least one of `text`/`html` is required by the API.
55#[derive(Clone, Debug, Default, Serialize)]
56pub struct ReplyToMessage {
57    /// Recipient override; empty means derive from the parent message.
58    #[serde(skip_serializing_if = "Vec::is_empty")]
59    pub to: Vec<String>,
60    /// Treat the reply as a reply-all (server-side convenience flag for
61    /// `reply_to_message`).
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub reply_all: Option<bool>,
64    /// Carbon-copy recipients (in addition to those on the thread).
65    #[serde(skip_serializing_if = "Vec::is_empty")]
66    pub cc: Vec<String>,
67    /// Blind-carbon-copy recipients.
68    #[serde(skip_serializing_if = "Vec::is_empty")]
69    pub bcc: Vec<String>,
70    /// Plain-text body.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub text: Option<String>,
73    /// HTML body.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub html: Option<String>,
76    /// Labels to attach to the reply.
77    #[serde(skip_serializing_if = "Vec::is_empty")]
78    pub labels: Vec<String>,
79    /// Files to attach.
80    #[serde(skip_serializing_if = "Vec::is_empty")]
81    pub attachments: Vec<SendAttachment>,
82    /// Extra headers to set on the outgoing message.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub headers: Option<serde_json::Value>,
85    /// Ask the API to make open tracking available for this reply.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub track_opens: Option<bool>,
88}
89
90/// The presigned download for a message's raw RFC 822 source, from
91/// `get_raw_message`. Fetch the bytes with `download_raw`.
92#[derive(Clone, Debug, Deserialize)]
93pub struct RawMessage {
94    /// The message id.
95    pub message_id: String,
96    /// Size of the raw message in bytes.
97    #[serde(default)]
98    pub size: Option<u64>,
99    /// Short-lived presigned URL to download the `.eml` bytes.
100    pub download_url: String,
101    /// When `download_url` expires (RFC 3339).
102    #[serde(default)]
103    pub expires_at: Option<String>,
104}
105
106/// Request body for `batch_get_messages`.
107#[derive(Clone, Debug, Default, Serialize)]
108pub struct BatchGetMessages {
109    /// The message ids to fetch.
110    pub message_ids: Vec<String>,
111}
112
113/// Response to `batch_get_messages`.
114#[derive(Clone, Debug, Deserialize)]
115pub struct BatchGetMessagesResponse {
116    /// The number of messages returned.
117    pub count: u64,
118    /// The requested messages.
119    #[serde(default)]
120    pub messages: Vec<Message>,
121}
122
123/// Request body for `batch_update_messages`: apply the same label
124/// changes to many messages at once.
125#[derive(Clone, Debug, Default, Serialize)]
126pub struct BatchUpdateMessages {
127    /// The message ids to update.
128    pub message_ids: Vec<String>,
129    /// Labels to add to each message.
130    #[serde(skip_serializing_if = "Vec::is_empty")]
131    pub add_labels: Vec<String>,
132    /// Labels to remove from each message.
133    #[serde(skip_serializing_if = "Vec::is_empty")]
134    pub remove_labels: Vec<String>,
135}
136
137/// Response to `batch_update_messages`.
138#[derive(Clone, Debug, Deserialize)]
139pub struct BatchUpdateMessagesResponse {
140    /// The number of messages updated.
141    pub count: u64,
142    /// The per-message results.
143    #[serde(default)]
144    pub updates: Vec<UpdatedMessage>,
145}
146/// Request body for `update_message`.
147#[derive(Clone, Debug, Default, Serialize)]
148pub struct UpdateMessage {
149    /// Labels to add.
150    #[serde(skip_serializing_if = "Vec::is_empty")]
151    pub add_labels: Vec<String>,
152    /// Labels to remove.
153    #[serde(skip_serializing_if = "Vec::is_empty")]
154    pub remove_labels: Vec<String>,
155}
156/// The API's response to `update_message`: the message id and its
157/// labels after the update.
158#[derive(Clone, Debug, Deserialize)]
159pub struct UpdatedMessage {
160    /// Id of the updated message.
161    pub message_id: String,
162    /// The message's labels after the update.
163    #[serde(default)]
164    pub labels: Vec<String>,
165}
166/// A message as the API returns it. List and search items are a subset of the
167/// full get-message shape; every non-id field defaults so all three parse.
168#[derive(Clone, Debug, Deserialize)]
169pub struct Message {
170    /// Unique id within the inbox.
171    pub message_id: String,
172    /// Inbox the message belongs to.
173    #[serde(default)]
174    pub inbox_id: Option<String>,
175    /// Conversation thread id.
176    #[serde(default)]
177    pub thread_id: Option<String>,
178    /// Sender address.
179    #[serde(default)]
180    pub from: Option<String>,
181    /// Recipient addresses.
182    #[serde(default)]
183    pub to: Vec<String>,
184    /// Addresses replied to, when set.
185    #[serde(default)]
186    pub reply_to: Vec<String>,
187    /// Carbon-copy recipients.
188    #[serde(default)]
189    pub cc: Vec<String>,
190    /// Blind-carbon-copy recipients.
191    #[serde(default)]
192    pub bcc: Vec<String>,
193    /// Subject line.
194    #[serde(default)]
195    pub subject: Option<String>,
196    /// Short plain-text excerpt (list responses).
197    #[serde(default)]
198    pub preview: Option<String>,
199    /// Full plain-text body (get responses).
200    #[serde(default)]
201    pub text: Option<String>,
202    /// Full HTML body (get responses).
203    #[serde(default)]
204    pub html: Option<String>,
205    /// Plain-text body extracted from attachments by the API.
206    #[serde(default)]
207    pub extracted_text: Option<String>,
208    /// HTML body extracted from attachments by the API.
209    #[serde(default)]
210    pub extracted_html: Option<String>,
211    /// Labels on the message (e.g. `received`, `unread`).
212    #[serde(default)]
213    pub labels: Vec<String>,
214    /// RFC 3339 send/receive timestamp.
215    #[serde(default)]
216    pub timestamp: Option<String>,
217    /// Attachments on the message (present in get-message responses).
218    #[serde(default)]
219    pub attachments: Vec<Attachment>,
220    /// `Message-ID` of the message this one replies to, when set.
221    #[serde(default)]
222    pub in_reply_to: Option<String>,
223    /// `References` chain of `Message-ID`s, when set.
224    #[serde(default)]
225    pub references: Vec<String>,
226    /// Extra headers on the message, when set.
227    #[serde(default)]
228    pub headers: Option<serde_json::Value>,
229    /// Calendar event attached to the message, when it carries an invite.
230    #[serde(default)]
231    pub calendar_event_id: Option<String>,
232    /// Total message size in bytes.
233    #[serde(default)]
234    pub size: Option<u64>,
235    /// Timestamp the message was last updated (RFC 3339).
236    #[serde(default)]
237    pub updated_at: Option<String>,
238    /// Timestamp the message was created (RFC 3339).
239    #[serde(default)]
240    pub created_at: Option<String>,
241    /// Search-highlight fragments, present in search responses.
242    #[serde(default)]
243    pub highlights: Option<serde_json::Value>,
244}
245/// One page of messages from `list_messages_page`.
246#[derive(Clone, Debug, Deserialize)]
247pub struct MessageList {
248    /// Total messages in the inbox (not just this page).
249    pub count: u64,
250    /// This page of messages.
251    #[serde(default)]
252    pub messages: Vec<Message>,
253    /// Cursor for the next page; `None` on the last page.
254    #[serde(default)]
255    pub next_page_token: Option<String>,
256}
257/// Filters for `list_messages_filtered` and
258/// `search_messages_page`. Pagination fields (`limit`,
259/// `page_token`) live here because they share the same query-parameter
260/// namespace as the filter fields.
261#[derive(Clone, Debug, Default)]
262pub struct MessageListFilters {
263    /// Maximum items per page.
264    pub limit: Option<u32>,
265    /// Cursor from a previous response's `next_page_token`.
266    pub page_token: Option<String>,
267    /// Filter by labels.
268    pub labels: Vec<String>,
269    /// Only messages before this timestamp (RFC 3339).
270    pub before: Option<String>,
271    /// Only messages after this timestamp (RFC 3339).
272    pub after: Option<String>,
273    /// Return oldest first instead of newest first.
274    pub ascending: Option<bool>,
275    /// Include spam messages.
276    pub include_spam: Option<bool>,
277    /// Include blocked messages.
278    pub include_blocked: Option<bool>,
279    /// Include unauthenticated messages.
280    pub include_unauthenticated: Option<bool>,
281    /// Include trashed messages.
282    pub include_trash: Option<bool>,
283    /// Filter by sender substring (repeatable).
284    pub from: Vec<String>,
285    /// Filter by recipient substring -- matches to, cc, or bcc (repeatable).
286    pub to: Vec<String>,
287    /// Filter by subject substring (repeatable).
288    pub subject: Vec<String>,
289}
290impl MessageListFilters {
291    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
292        QueryBuilder::new()
293            .opt("limit", self.limit.as_ref())
294            .opt("page_token", self.page_token.as_ref())
295            .many("labels", &self.labels)
296            .opt("before", self.before.as_ref())
297            .opt("after", self.after.as_ref())
298            .opt("ascending", self.ascending.as_ref())
299            .opt("include_spam", self.include_spam.as_ref())
300            .opt("include_blocked", self.include_blocked.as_ref())
301            .opt(
302                "include_unauthenticated",
303                self.include_unauthenticated.as_ref(),
304            )
305            .opt("include_trash", self.include_trash.as_ref())
306            .many("from", &self.from)
307            .many("to", &self.to)
308            .many("subject", &self.subject)
309            .build()
310    }
311}