Skip to main content

agentmail/types/
mod.rs

1//! Wire shapes from the AgentMail OpenAPI spec. Every type deserializes
2//! permissively (unknown fields ignored, optional fields default) so spec
3//! additions don't break callers; request types skip empty fields so the
4//! API's validators stay quiet.
5
6use crate::util::QueryBuilder;
7
8mod agent;
9mod api_keys;
10mod attachments;
11mod auth;
12mod domains;
13mod drafts;
14mod inbox_events;
15mod inboxes;
16mod lists;
17mod messages;
18mod metrics;
19mod organizations;
20mod pods;
21mod threads;
22mod webhooks;
23
24pub use agent::*;
25pub use api_keys::*;
26pub use attachments::*;
27pub use auth::*;
28pub use domains::*;
29pub use drafts::*;
30pub use inbox_events::*;
31pub use inboxes::*;
32pub use lists::*;
33pub use messages::*;
34pub use metrics::*;
35pub use organizations::*;
36pub use pods::*;
37pub use threads::*;
38pub use webhooks::*;
39
40/// Pagination controls for the `list_*_page` calls. `Default` is the API's
41/// own defaults (first page, server-chosen page size).
42#[derive(Clone, Debug, Default)]
43pub struct Page {
44    /// Maximum items per page (the API caps this server-side).
45    pub limit: Option<u32>,
46    /// Cursor from a previous response's `next_page_token`.
47    pub page_token: Option<String>,
48}
49impl Page {
50    pub(crate) fn query(&self) -> Vec<(&'static str, String)> {
51        QueryBuilder::new()
52            .opt("limit", self.limit.as_ref())
53            .opt("page_token", self.page_token.as_ref())
54            .build()
55    }
56}
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn wire_shapes_round_trip() {
63        // Response example from the OpenAPI spec.
64        let inbox: Inbox = serde_json::from_str(
65            r#"{"pod_id":"pod_1","inbox_id":"ib_1","email":"x@agentmail.to",
66                "display_name":"X","updated_at":"2024-01-15T09:30:00Z",
67                "created_at":"2024-01-15T09:30:00Z","surprise_field":1}"#,
68        )
69        .unwrap();
70        assert_eq!(inbox.email, "x@agentmail.to");
71
72        // List items are a subset of the get shape, both must parse.
73        let list: MessageList = serde_json::from_str(
74            r#"{"count":1,"messages":[{"message_id":"m1","thread_id":"t1",
75                "from":"a@b.c","subject":"hi","preview":"…","timestamp":"2026-01-01T00:00:00Z"}]}"#,
76        )
77        .unwrap();
78        assert_eq!(list.messages[0].message_id, "m1");
79        assert!(list.messages[0].text.is_none());
80
81        // Requests omit empties so the API's validators stay quiet.
82        let body = serde_json::to_value(SendMessage {
83            to: vec!["a@b.c".into()],
84            subject: Some("s".into()),
85            text: Some("t".into()),
86            ..Default::default()
87        })
88        .unwrap();
89        assert_eq!(
90            body,
91            serde_json::json!({"to":["a@b.c"],"subject":"s","text":"t"}),
92        );
93    }
94
95    #[test]
96    fn page_query_pairs() {
97        assert!(Page::default().query().is_empty());
98        let q = Page {
99            limit: Some(10),
100            page_token: Some("tok".into()),
101        }
102        .query();
103        assert_eq!(
104            q,
105            vec![
106                ("limit", "10".to_string()),
107                ("page_token", "tok".to_string())
108            ],
109        );
110    }
111}