1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3pub use client::Resend;
54pub use config::{Config, ConfigBuilder};
55pub use serde_json::{Value, json};
56
57mod api_keys;
58mod batch;
59mod broadcasts;
60mod client;
61mod config;
62mod contacts;
63mod domains;
64mod emails;
65mod error;
66pub mod events;
67pub mod idempotent;
68pub mod list_opts;
69pub mod rate_limit;
70mod receiving;
71mod segments;
72mod templates;
73mod topics;
74mod webhooks;
75
76pub mod services {
77 pub use super::api_keys::ApiKeysSvc;
80 pub use super::batch::BatchSvc;
81 pub use super::broadcasts::BroadcastsSvc;
82 pub use super::contacts::ContactsSvc;
83 pub use super::domains::DomainsSvc;
84 pub use super::emails::EmailsSvc;
85 pub use super::receiving::ReceivingSvc;
86 pub use super::segments::SegmentsSvc;
87 pub use super::templates::TemplateSvc;
88 pub use super::topics::TopicsSvc;
89}
90
91pub mod types {
92 pub use super::api_keys::types::{
95 ApiKey, ApiKeyId, ApiKeyToken, CreateApiKeyOptions, Permission,
96 };
97 pub use super::batch::types::{
98 BatchValidation, PermissiveBatchErrors, SendEmailBatchPermissiveResponse,
99 SendEmailBatchResponse,
100 };
101 pub use super::broadcasts::types::{
102 Broadcast, BroadcastId, CreateBroadcastOptions, CreateBroadcastResponse,
103 RemoveBroadcastResponse, SendBroadcastOptions, SendBroadcastResponse,
104 UpdateBroadcastOptions, UpdateBroadcastResponse,
105 };
106 pub use super::contacts::types::{
107 AddContactSegmentResponse, Contact, ContactChanges, ContactId, ContactProperty,
108 ContactPropertyChanges, ContactPropertyId, ContactTopic, CreateContactOptions,
109 CreateContactPropertyOptions, CreateContactPropertyResponse, DeleteContactPropertyResponse,
110 PropertyType, RemoveContactSegmentResponse, UpdateContactPropertyResponse,
111 UpdateContactTopicOptions,
112 };
113 pub use super::domains::types::{
114 CreateDomainOptions, DkimRecordType, Domain, DomainChanges, DomainDkimRecord, DomainId,
115 DomainRecord, DomainSpfRecord, DomainStatus, ProxyStatus, ReceivingRecord,
116 ReceivingRecordType, Region, SpfRecordType, Tls, UpdateDomainResponse,
117 };
118 pub use super::emails::types::{
119 Attachment, CancelScheduleResponse, ContentOrPath, CreateAttachment,
120 CreateEmailBaseOptions, CreateEmailResponse, Email, EmailEvent, EmailId, EmailTemplate,
121 Tag, UpdateEmailOptions, UpdateEmailResponse,
122 };
123 pub use super::error::types::{ErrorKind, ErrorResponse};
124 pub use super::receiving::types::{
125 InboundAttachment, InboundAttatchmentId, InboundEmail, InboundEmailId,
126 };
127 pub use super::segments::types::{CreateSegmentResponse, Segment, SegmentId};
128 pub use super::templates::types::{
129 CreateTemplateOptions, CreateTemplateResponse, DeleteTemplateResponse,
130 DuplicateTemplateResponse, PublishTemplateResponse, Template, TemplateEvent, TemplateId,
131 UpdateTemplateOptions, UpdateTemplateResponse, Variable, VariableType,
132 };
133 pub use super::topics::types::{
134 CreateTopicOptions, CreateTopicResponse, DeleteTopicResponse, SubscriptionType, Topic,
135 TopicId, TopicVisibility, UpdateTopicOptions, UpdateTopicResponse,
136 };
137 pub use super::webhooks::types::{
138 CreateWebhookOptions, CreateWebhookResponse, DeleteWebhookResponse, UpdateWebhookOptions,
139 UpdateWebhookResponse, Webhook, WebhookId, WebhookStatus,
140 };
141}
142
143#[derive(Debug, thiserror::Error)]
147pub enum Error {
148 #[error("http error: {0}")]
150 Http(#[from] reqwest::Error),
151
152 #[error("resend error: {0}")]
154 Resend(#[from] types::ErrorResponse),
155
156 #[error("Failed to parse Resend API response. Received: \n{0}")]
158 Parse(String),
159
160 #[error("Too many requests. Limit is {ratelimit_limit:?} per {ratelimit_reset:?} seconds.")]
163 RateLimit {
164 ratelimit_limit: Option<u64>,
165 ratelimit_remaining: Option<u64>,
166 ratelimit_reset: Option<u64>,
167 },
168}
169
170macro_rules! define_id_type {
171 ($name:ident) => {
172 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
174 pub struct $name(ecow::EcoString);
175
176 impl $name {
177 #[inline]
179 #[must_use]
180 pub fn new(id: &str) -> Self {
181 Self(ecow::EcoString::from(id))
182 }
183 }
184
185 impl std::ops::Deref for $name {
186 type Target = str;
187
188 #[inline]
189 fn deref(&self) -> &Self::Target {
190 self.as_ref()
191 }
192 }
193
194 impl AsRef<str> for $name {
195 #[inline]
196 fn as_ref(&self) -> &str {
197 self.0.as_str()
198 }
199 }
200
201 impl std::fmt::Display for $name {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 std::fmt::Display::fmt(&self.0, f)
204 }
205 }
206 };
207}
208
209pub(crate) use define_id_type;
210
211pub type Result<T, E = Error> = std::result::Result<T, E>;
215
216#[cfg(test)]
217mod test {
218 use std::sync::LazyLock;
219
220 use crate::{Error, Resend};
221
222 #[allow(dead_code, clippy::redundant_pub_crate)]
223 pub(crate) struct LocatedError<E: std::error::Error + 'static> {
224 inner: E,
225 location: &'static std::panic::Location<'static>,
226 }
227
228 impl From<Error> for LocatedError<Error> {
229 #[track_caller]
230 fn from(value: Error) -> Self {
231 Self {
232 inner: value,
233 location: std::panic::Location::caller(),
234 }
235 }
236 }
237
238 impl<T: std::error::Error + 'static> std::fmt::Debug for LocatedError<T> {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(
241 f,
242 "{}:{}:{}\n{:?}",
243 self.location.file(),
244 self.location.line(),
245 self.location.column(),
246 self.inner
247 )
248 }
249 }
250
251 #[allow(clippy::redundant_pub_crate)]
252 pub(crate) type DebugResult<T, E = LocatedError<Error>> = Result<T, E>;
253
254 #[allow(clippy::redundant_pub_crate)]
255 pub(crate) static CLIENT: LazyLock<Resend> = LazyLock::new(Resend::default);
262
263 #[allow(clippy::redundant_pub_crate)]
265 pub(crate) async fn retry<O, E, F>(
266 mut f: F,
267 retries: i32,
268 interval: std::time::Duration,
269 ) -> Result<O, E>
270 where
271 F: AsyncFnMut() -> Result<O, E>,
272 {
273 let mut count = 0;
274 loop {
275 match f().await {
276 Ok(output) => break Ok(output),
277 Err(e) => {
278 println!("try {count} failed");
279 count += 1;
280 if count == retries {
281 return Err(e);
282 }
283 tokio::time::sleep(interval).await;
284 }
285 }
286 }
287 }
288}