Skip to main content

axene_mailer/resources/
emails.rs

1//! The `emails` resource: send, look up, search, schedule, and inspect messages.
2
3use reqwest::Method;
4use serde::Deserialize;
5use serde_json::Value;
6
7use super::{query, urlencode};
8use crate::error::Result;
9use crate::http::HttpTransport;
10use crate::models::{
11    BatchResponse, Email, EmailDetail, EmailEvent, EmailSearchHit, IdStatus, ScheduledEmail,
12    SendEmail, SendEmailResponse, ValidationResult,
13};
14
15/// Accessed as `client.emails()`.
16#[derive(Debug, Clone)]
17pub struct Emails {
18    http: HttpTransport,
19}
20
21#[derive(Deserialize)]
22struct SavedSearches {
23    searches: Vec<Value>,
24}
25
26impl Emails {
27    pub(crate) fn new(http: HttpTransport) -> Self {
28        Self { http }
29    }
30
31    /// Send a single email.
32    pub async fn send(&self, message: &SendEmail) -> Result<SendEmailResponse> {
33        self.http
34            .request_json(Method::POST, "/v1/emails/", message)
35            .await
36    }
37
38    /// Send up to your plan's batch limit in one call. The API accepts a bare
39    /// array of messages and returns a per-message result set.
40    pub async fn send_batch(&self, messages: &[SendEmail]) -> Result<BatchResponse> {
41        self.http
42            .request_json(Method::POST, "/v1/emails/batch", &messages)
43            .await
44    }
45
46    /// Dry-run a send: check whether the message would be accepted without
47    /// actually sending it.
48    pub async fn validate(&self, message: &SendEmail) -> Result<ValidationResult> {
49        self.http
50            .request_json(Method::POST, "/v1/emails/validate", message)
51            .await
52    }
53
54    /// List recent emails, newest first. `page` is zero-based.
55    pub async fn list(
56        &self,
57        status: Option<&str>,
58        page: Option<u64>,
59        limit: Option<u64>,
60    ) -> Result<Vec<Email>> {
61        let q = query(&[
62            ("status", status.map(String::from)),
63            ("page", page.map(|p| p.to_string())),
64            ("limit", limit.map(|l| l.to_string())),
65        ]);
66        self.http
67            .request(Method::GET, &format!("/v1/emails/{q}"))
68            .await
69    }
70
71    /// Fetch a single email with its bodies and events.
72    pub async fn get(&self, id: &str) -> Result<EmailDetail> {
73        self.http
74            .request(Method::GET, &format!("/v1/emails/{}", urlencode(id)))
75            .await
76    }
77
78    /// List delivery / open / click / bounce events for an email.
79    pub async fn events(&self, id: &str) -> Result<Vec<EmailEvent>> {
80        self.http
81            .request(
82                Method::GET,
83                &format!("/v1/emails/{}/events", urlencode(id)),
84            )
85            .await
86    }
87
88    /// Re-send a bounced, rejected, or failed email as a new message.
89    pub async fn retry(&self, id: &str) -> Result<SendEmailResponse> {
90        self.http
91            .request(Method::POST, &format!("/v1/emails/{}/retry", urlencode(id)))
92            .await
93    }
94
95    /// Search emails. `q` supports inline tokens (`to:`, `from:`, `status:`,
96    /// `domain:`, `tag:`); leftover words are matched as free text.
97    pub async fn search(
98        &self,
99        q: Option<&str>,
100        status: Option<&str>,
101        tag: Option<&str>,
102        page: Option<u64>,
103        limit: Option<u64>,
104    ) -> Result<Vec<EmailSearchHit>> {
105        let qs = query(&[
106            ("q", q.map(String::from)),
107            ("status", status.map(String::from)),
108            ("tag", tag.map(String::from)),
109            ("page", page.map(|p| p.to_string())),
110            ("limit", limit.map(|l| l.to_string())),
111        ]);
112        self.http
113            .request(Method::GET, &format!("/v1/emails/search{qs}"))
114            .await
115    }
116
117    /// List emails scheduled for future delivery, soonest first.
118    pub async fn list_scheduled(&self) -> Result<Vec<ScheduledEmail>> {
119        self.http
120            .request(Method::GET, "/v1/emails/scheduled")
121            .await
122    }
123
124    /// Cancel a scheduled email.
125    pub async fn cancel_scheduled(&self, id: &str) -> Result<IdStatus> {
126        self.http
127            .request(
128                Method::DELETE,
129                &format!("/v1/emails/scheduled/{}", urlencode(id)),
130            )
131            .await
132    }
133
134    /// Send a scheduled email immediately instead of waiting.
135    pub async fn send_scheduled_now(&self, id: &str) -> Result<IdStatus> {
136        self.http
137            .request(
138                Method::POST,
139                &format!("/v1/emails/scheduled/{}/send-now", urlencode(id)),
140            )
141            .await
142    }
143
144    /// Poll for emails whose status changed at or after `since` (ISO 8601).
145    /// Capped at 50 rows.
146    pub async fn updates(&self, since: &str) -> Result<Vec<Email>> {
147        let q = query(&[("since", Some(since.to_string()))]);
148        self.http
149            .request(Method::GET, &format!("/v1/emails/updates{q}"))
150            .await
151    }
152
153    /// Get the caller's saved searches.
154    pub async fn get_saved_searches(&self) -> Result<Vec<Value>> {
155        let r: SavedSearches = self
156            .http
157            .request(Method::GET, "/v1/emails/saved-searches")
158            .await?;
159        Ok(r.searches)
160    }
161
162    /// Replace the caller's saved searches (max 50).
163    pub async fn set_saved_searches(&self, searches: Vec<Value>) -> Result<Vec<Value>> {
164        let body = serde_json::json!({ "searches": searches });
165        let r: SavedSearches = self
166            .http
167            .request_json(Method::PUT, "/v1/emails/saved-searches", &body)
168            .await?;
169        Ok(r.searches)
170    }
171}