Skip to main content

sendry/resources/
suppression.rs

1//! Suppression list (hard-bounced / complained addresses we won't send to).
2
3use reqwest::Method;
4use serde::{Deserialize, Serialize};
5
6use crate::{client::Sendry, error::Error, DeleteResponse, Page, PaginationParams};
7
8/// Suppression resource handle.
9#[derive(Debug, Clone)]
10pub struct Suppression {
11    client: Sendry,
12}
13
14impl Suppression {
15    pub(crate) fn new(client: Sendry) -> Self {
16        Self { client }
17    }
18
19    /// List suppressed addresses.
20    pub async fn list(&self, params: PaginationParams) -> Result<Page<SuppressionEntry>, Error> {
21        let q = params.to_query();
22        self.client
23            .request(
24                self.client
25                    .build::<()>(Method::GET, "/v1/suppression", &q, None),
26            )
27            .await
28    }
29
30    /// Add an address to the suppression list.
31    pub async fn add(&self, params: AddSuppression) -> Result<SuppressionEntry, Error> {
32        self.client
33            .request(
34                self.client
35                    .build(Method::POST, "/v1/suppression", &[], Some(&params)),
36            )
37            .await
38    }
39
40    /// Remove an address.
41    pub async fn remove(&self, email: &str) -> Result<DeleteResponse, Error> {
42        let encoded = urlencode(email);
43        self.client
44            .request(self.client.build::<()>(
45                Method::DELETE,
46                &format!("/v1/suppression/{encoded}"),
47                &[],
48                None,
49            ))
50            .await
51    }
52}
53
54/// Parameters for [`Suppression::add`].
55#[derive(Debug, Clone, Serialize)]
56pub struct AddSuppression {
57    /// Email address.
58    pub email: String,
59    /// Reason: `hard_bounce`, `complaint`, `unsubscribe`, or `manual`.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub reason: Option<String>,
62}
63
64/// One suppression row.
65#[derive(Debug, Clone, Deserialize)]
66pub struct SuppressionEntry {
67    /// Email address.
68    pub email: String,
69    /// Reason it was added.
70    pub reason: String,
71    /// When added.
72    pub created_at: String,
73}
74
75fn urlencode(s: &str) -> String {
76    let mut out = String::with_capacity(s.len());
77    for b in s.bytes() {
78        match b {
79            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
80                out.push(b as char);
81            }
82            _ => out.push_str(&format!("%{b:02X}")),
83        }
84    }
85    out
86}