#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
pub use client::Resend;
pub use config::{Config, ConfigBuilder};
pub use serde_json::{Value, json};
mod api_keys;
mod automations;
mod batch;
mod broadcasts;
mod client;
mod config;
mod contacts;
mod domains;
mod emails;
mod error;
pub mod events;
pub mod idempotent;
pub mod list_opts;
mod logs;
pub mod rate_limit;
mod receiving;
mod segments;
mod templates;
mod topics;
mod webhooks;
pub mod services {
pub use super::api_keys::ApiKeysSvc;
pub use super::automations::AutomationsSvc;
pub use super::batch::BatchSvc;
pub use super::broadcasts::BroadcastsSvc;
pub use super::contacts::ContactsSvc;
pub use super::domains::DomainsSvc;
pub use super::emails::EmailsSvc;
pub use super::logs::LogsSvc;
pub use super::receiving::ReceivingSvc;
pub use super::segments::SegmentsSvc;
pub use super::templates::TemplateSvc;
pub use super::topics::TopicsSvc;
}
pub mod types {
pub use super::api_keys::types::{
ApiKey, ApiKeyId, ApiKeyToken, CreateApiKeyOptions, Permission,
};
pub use super::automations::types::{
AddToSegmentStepConfig, Automation, AutomationId, AutomationMinimal, AutomationRun,
AutomationRunId, AutomationStatus, AutomationTemplate, Connection, ConnectionType,
CreateAutomationOptions, CreateAutomationResponse, DelayStepConfig,
DeleteAutomationResponse, SendEmailStepConfig, Step, StopAutomationResponse,
TriggerStepConfig, UpdateAutomationOptions, UpdateAutomationResponse,
WaitForEventStepConfig,
};
pub use super::batch::types::{
BatchValidation, PermissiveBatchErrors, SendEmailBatchPermissiveResponse,
SendEmailBatchResponse,
};
pub use super::broadcasts::types::{
Broadcast, BroadcastId, CreateBroadcastOptions, CreateBroadcastResponse,
RemoveBroadcastResponse, SendBroadcastOptions, SendBroadcastResponse,
UpdateBroadcastOptions, UpdateBroadcastResponse,
};
pub use super::contacts::types::{
AddContactSegmentResponse, Contact, ContactChanges, ContactId, ContactProperty,
ContactPropertyChanges, ContactPropertyId, ContactTopic, CreateContactOptions,
CreateContactPropertyOptions, CreateContactPropertyResponse, DeleteContactPropertyResponse,
PropertyType, RemoveContactSegmentResponse, UpdateContactPropertyResponse,
UpdateContactTopicOptions,
};
pub use super::domains::types::{
CreateDomainOptions, DkimRecordType, Domain, DomainCapabilities, DomainCapabilityStatus,
DomainChanges, DomainDkimRecord, DomainId, DomainRecord, DomainSpfRecord, DomainStatus,
ProxyStatus, ReceivingRecord, ReceivingRecordType, Region, SpfRecordType, Tls,
UpdateDomainResponse, VerifyDomainResponse,
};
pub use super::emails::types::{
Attachment, CancelScheduleResponse, ContentDisposition, ContentOrPath, CreateAttachment,
CreateEmailBaseOptions, CreateEmailResponse, Email, EmailEvent, EmailId, EmailTemplate,
Tag, UpdateEmailOptions, UpdateEmailResponse,
};
pub use super::error::types::{ErrorKind, ErrorResponse};
pub use super::events::types::{
ContactIdOrEmail, CreateEventOptions, CreateEventResponse, DeleteEventResponse,
GetEventResponse, SendEventOptions, SendEventResponse, UpdateEventOptions,
UpdateEventResponse,
};
pub use super::logs::types::Log;
pub use super::receiving::types::{
ForwardInboundEmailResponse, ForwardReceivingEmail, InboundAttachment, InboundAttachmentId,
InboundEmail, InboundEmailId,
};
pub use super::segments::types::{CreateSegmentResponse, Segment, SegmentId};
pub use super::templates::types::{
CreateTemplateOptions, CreateTemplateResponse, DeleteTemplateResponse,
DuplicateTemplateResponse, PublishTemplateResponse, Template, TemplateEvent, TemplateId,
UpdateTemplateOptions, UpdateTemplateResponse, Variable, VariableType,
};
pub use super::topics::types::{
CreateTopicOptions, CreateTopicResponse, DeleteTopicResponse, SubscriptionType, Topic,
TopicId, TopicVisibility, UpdateTopicOptions, UpdateTopicResponse,
};
pub use super::webhooks::types::{
CreateWebhookOptions, CreateWebhookResponse, DeleteWebhookResponse, UpdateWebhookOptions,
UpdateWebhookResponse, Webhook, WebhookId, WebhookStatus,
};
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
#[error("resend error: {0}")]
Resend(#[from] types::ErrorResponse),
#[error("Failed to parse Resend API response. Received: \n{0}")]
Parse(String),
#[error("Too many requests. Limit is {ratelimit_limit:?} per {ratelimit_reset:?} seconds.")]
RateLimit {
ratelimit_limit: Option<u64>,
ratelimit_remaining: Option<u64>,
ratelimit_reset: Option<u64>,
},
}
macro_rules! define_id_type {
($name:ident) => {
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
pub struct $name(ecow::EcoString);
impl $name {
#[inline]
#[must_use]
pub fn new(id: &str) -> Self {
Self(ecow::EcoString::from(id))
}
}
impl std::ops::Deref for $name {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl AsRef<str> for $name {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
};
}
pub(crate) use define_id_type;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[cfg(test)]
mod test {
use std::sync::LazyLock;
use crate::{Error, Resend};
#[allow(dead_code, clippy::redundant_pub_crate)]
pub(crate) struct LocatedError<E: std::error::Error + 'static> {
inner: E,
location: &'static std::panic::Location<'static>,
}
impl From<Error> for LocatedError<Error> {
#[track_caller]
fn from(value: Error) -> Self {
Self {
inner: value,
location: std::panic::Location::caller(),
}
}
}
impl<T: std::error::Error + 'static> std::fmt::Debug for LocatedError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}\n{:?}",
self.location.file(),
self.location.line(),
self.location.column(),
self.inner
)
}
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) type DebugResult<T, E = LocatedError<Error>> = Result<T, E>;
#[allow(clippy::redundant_pub_crate)]
pub(crate) static CLIENT: LazyLock<Resend> = LazyLock::new(Resend::default);
#[allow(clippy::redundant_pub_crate)]
pub(crate) async fn retry<O, E, F>(
mut f: F,
retries: i32,
interval: std::time::Duration,
) -> Result<O, E>
where
F: AsyncFnMut() -> Result<O, E>,
{
let mut count = 0;
loop {
match f().await {
Ok(output) => break Ok(output),
Err(e) => {
println!("try {count} failed");
count += 1;
if count == retries {
return Err(e);
}
tokio::time::sleep(interval).await;
}
}
}
}
}