1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3pub use client::Resend;
54pub use config::{Config, ConfigBuilder};
55pub use reqwest::Method;
56pub use serde_json::{Value, json};
57
58mod api_keys;
59mod automations;
60mod batch;
61mod broadcasts;
62mod client;
63mod config;
64mod contacts;
65mod domains;
66mod emails;
67mod error;
68pub mod events;
69pub mod idempotent;
70pub mod list_opts;
71mod logs;
72mod oauth;
73pub mod rate_limit;
74mod receiving;
75mod segments;
76mod suppressions;
77mod templates;
78mod topics;
79mod webhooks;
80
81pub mod services {
82 pub use super::api_keys::ApiKeysSvc;
85 pub use super::automations::AutomationsSvc;
86 pub use super::batch::BatchSvc;
87 pub use super::broadcasts::BroadcastsSvc;
88 pub use super::contacts::ContactsSvc;
89 pub use super::domains::DomainsSvc;
90 pub use super::emails::EmailsSvc;
91 pub use super::logs::LogsSvc;
92 pub use super::oauth::OAuthSvc;
93 pub use super::receiving::ReceivingSvc;
94 pub use super::segments::SegmentsSvc;
95 pub use super::suppressions::SuppressionsSvc;
96 pub use super::templates::TemplateSvc;
97 pub use super::topics::TopicsSvc;
98}
99
100pub mod types {
101 pub use super::api_keys::types::{
104 ApiKey, ApiKeyId, ApiKeyToken, CreateApiKeyOptions, Permission, UpdateApiKeyOptions,
105 UpdateApiKeyResponse,
106 };
107 pub use super::automations::types::{
108 AddToSegmentStepConfig, Automation, AutomationId, AutomationMinimal, AutomationRun,
109 AutomationRunId, AutomationStatus, AutomationTemplate, Connection, ConnectionType,
110 CreateAutomationOptions, CreateAutomationResponse, DelayStepConfig,
111 DeleteAutomationResponse, DuplicateAutomationResponse, SendEmailStepConfig, Step,
112 StopAutomationResponse, TriggerStepConfig, UpdateAutomationOptions,
113 UpdateAutomationResponse, WaitForEventStepConfig,
114 };
115 pub use super::batch::types::{
116 BatchValidation, PermissiveBatchErrors, SendEmailBatchPermissiveResponse,
117 SendEmailBatchResponse,
118 };
119 pub use super::broadcasts::types::{
120 Broadcast, BroadcastClickedLink, BroadcastId, BroadcastRecipient,
121 BroadcastRecipientBounceType, BroadcastRecipientClickedLink, BroadcastRecipientEventType,
122 CancelBroadcastResponse, CreateBroadcastOptions, CreateBroadcastResponse,
123 DuplicateBroadcastResponse, ListRecipientsOptions, RemoveBroadcastResponse,
124 SendBroadcastOptions, SendBroadcastResponse, UpdateBroadcastOptions,
125 UpdateBroadcastResponse,
126 };
127 pub use super::contacts::types::{
128 AddContactSegmentResponse, Contact, ContactChanges, ContactId, ContactImport,
129 ContactImportColumnMap, ContactImportCounts, ContactImportId, ContactImportOnConflict,
130 ContactImportPropertyMapping, ContactImportPropertyType, ContactImportStatus,
131 ContactImportTopic, ContactImportTopicSubscription, ContactProperty,
132 ContactPropertyChanges, ContactPropertyId, ContactTopic, CreateContactImportOptions,
133 CreateContactImportResponse, CreateContactOptions, CreateContactPropertyOptions,
134 CreateContactPropertyResponse, DeleteContactPropertyResponse, PropertyType,
135 RemoveContactSegmentResponse, SegmentObject, UpdateContactPropertyResponse,
136 UpdateContactTopicOptions,
137 };
138 pub use super::domains::types::{
139 CreateDomainClaimOptions, CreateDomainOptions, DkimRecordType, Domain, DomainCapabilities,
140 DomainCapabilityStatus, DomainChanges, DomainClaim, DomainClaimBlockedReason,
141 DomainClaimId, DomainClaimRecord, DomainClaimRecordType, DomainClaimStatus,
142 DomainDkimRecord, DomainId, DomainRecord, DomainRecordStatus, DomainSpfRecord,
143 DomainStatus, ProxyStatus, ReceivingRecord, ReceivingRecordType, Region, SpfRecordType,
144 Tls, UpdateDomainResponse, VerifyDomainResponse,
145 };
146 pub use super::emails::types::{
147 Attachment, CancelScheduleResponse, ContentDisposition, ContentOrPath, CreateAttachment,
148 CreateEmailBaseOptions, CreateEmailResponse, Dimension, Email, EmailEvent, EmailId,
149 EmailMetrics, EmailMetricsDataPoint, EmailTemplate, GetEmailMetricsOptions, Metric,
150 MetricsGranularity, ShareEmailOptions, ShareEmailResponse, Tag, UpdateEmailOptions,
151 UpdateEmailResponse,
152 };
153 pub use super::error::types::{ErrorKind, ErrorResponse};
154 pub use super::events::types::{
155 ContactIdOrEmail, CreateEventOptions, CreateEventResponse, DeleteEventResponse,
156 GetEventResponse, SendEventOptions, SendEventResponse, UpdateEventOptions,
157 UpdateEventResponse,
158 };
159 pub use super::logs::types::{Log, LogId};
160 pub use super::oauth::types::{
161 ClientId, OAuthGrant, OAuthGrantClient, OAuthGrantId, RevokeOAuthGrantResponse,
162 };
163 pub use super::receiving::types::{
164 ForwardInboundEmailResponse, ForwardReceivingEmail, GetInboundEmailOptions,
165 GetInboundEmailRaw, InboundAttachment, InboundAttachmentId, InboundEmail,
166 InboundEmailHtmlFormat, InboundEmailId,
167 };
168 pub use super::segments::types::{
169 CreateSegmentResponse, Segment, SegmentId, UpdateSegmentResponse,
170 };
171 pub use super::suppressions::types::{
172 AddSuppressionOptions, AddSuppressionResponse, BatchAddSuppressionOptions,
173 BatchAddSuppressionResponse, BatchRemoveSuppressionOptions,
174 BatchRemoveSuppressionsResponse, EmailsSpecified, IdsSpecified, NotSpecified,
175 RemoveSuppressionResponse, Suppression, SuppressionId, SuppressionOrigin,
176 };
177 pub use super::templates::types::{
178 CreateTemplateOptions, CreateTemplateResponse, DeleteTemplateResponse,
179 DuplicateTemplateResponse, PublishTemplateResponse, Template, TemplateEvent, TemplateId,
180 UpdateTemplateOptions, UpdateTemplateResponse, Variable, VariableType,
181 };
182 pub use super::topics::types::{
183 CreateTopicOptions, CreateTopicResponse, DeleteTopicResponse, SubscriptionType, Topic,
184 TopicId, TopicVisibility, UpdateTopicOptions, UpdateTopicResponse,
185 };
186 pub use super::webhooks::types::{
187 CreateWebhookOptions, CreateWebhookResponse, DeleteWebhookResponse,
188 ReplayWebhookEventResponse, RotateWebhookSigningSecretResponse, UpdateWebhookOptions,
189 UpdateWebhookResponse, Webhook, WebhookEvent, WebhookEventAttempt, WebhookEventAttemptId,
190 WebhookEventAttemptListResponse, WebhookEventDetails, WebhookEventId,
191 WebhookEventListResponse, WebhookEventStatus, WebhookId, WebhookStatus,
192 };
193}
194
195#[derive(Debug, thiserror::Error)]
199pub enum Error {
200 #[error("http error: {0}")]
202 Http(#[from] reqwest::Error),
203
204 #[error("resend error: {0}")]
206 Resend(#[from] types::ErrorResponse),
207
208 #[error("Failed to parse Resend API response. Received: \n{message}")]
210 Parse {
211 message: String,
212 source: Option<Box<dyn std::error::Error + Send + Sync>>,
213 },
214
215 #[error("{0}")]
217 Other(String),
218
219 #[error("Too many requests. Limit is {ratelimit_limit:?} per {ratelimit_reset:?} seconds.")]
222 RateLimit {
223 ratelimit_limit: Option<u64>,
224 ratelimit_remaining: Option<u64>,
225 ratelimit_reset: Option<u64>,
226 },
227}
228
229macro_rules! define_id_type {
230 ($name:ident) => {
231 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
233 pub struct $name(ecow::EcoString);
234
235 impl $name {
236 #[inline]
237 #[must_use]
238 #[doc = concat!("Creates a new [`", stringify!($name), "`].")]
239 pub fn new(id: &str) -> Self {
240 Self(ecow::EcoString::from(id))
241 }
242 }
243
244 impl std::ops::Deref for $name {
245 type Target = str;
246
247 #[inline]
248 fn deref(&self) -> &Self::Target {
249 self.as_ref()
250 }
251 }
252
253 impl AsRef<str> for $name {
254 #[inline]
255 fn as_ref(&self) -> &str {
256 self.0.as_str()
257 }
258 }
259
260 impl std::fmt::Display for $name {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 std::fmt::Display::fmt(&self.0, f)
263 }
264 }
265 };
266}
267
268pub(crate) use define_id_type;
269
270pub type Result<T, E = Error> = std::result::Result<T, E>;
274
275#[cfg(test)]
276mod test {
277 use std::sync::LazyLock;
278
279 use crate::{Error, Resend};
280
281 #[allow(dead_code, clippy::redundant_pub_crate)]
282 pub(crate) struct LocatedError<E: std::error::Error + 'static> {
283 inner: E,
284 location: &'static std::panic::Location<'static>,
285 }
286
287 impl From<Error> for LocatedError<Error> {
288 #[track_caller]
289 fn from(value: Error) -> Self {
290 Self {
291 inner: value,
292 location: std::panic::Location::caller(),
293 }
294 }
295 }
296
297 impl<T: std::error::Error + 'static> std::fmt::Debug for LocatedError<T> {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 write!(
300 f,
301 "{}:{}:{}\n{:?}",
302 self.location.file(),
303 self.location.line(),
304 self.location.column(),
305 self.inner
306 )
307 }
308 }
309
310 #[allow(clippy::redundant_pub_crate)]
311 pub(crate) type DebugResult<T, E = LocatedError<Error>> = Result<T, E>;
312
313 #[allow(clippy::redundant_pub_crate)]
314 pub(crate) static CLIENT: LazyLock<Resend> = LazyLock::new(Resend::default);
321
322 #[allow(dead_code, clippy::redundant_pub_crate)]
324 pub(crate) async fn retry<O, E, F>(
325 mut f: F,
326 retries: i32,
327 interval: std::time::Duration,
328 ) -> Result<O, E>
329 where
330 F: AsyncFnMut() -> Result<O, E>,
331 {
332 let mut count = 0;
333 loop {
334 match f().await {
335 Ok(output) => break Ok(output),
336 Err(e) => {
337 println!("try {count} failed");
338 count += 1;
339 if count == retries {
340 return Err(e);
341 }
342 tokio::time::sleep(interval).await;
343 }
344 }
345 }
346 }
347}