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