1use std::sync::{Arc, OnceLock};
2
3use crate::{MailConfig, MailError, Mailable};
4
5tokio::task_local! {
6 pub(crate) static CURRENT_MAIL_CONFIG: Arc<MailConfig>;
7}
8
9pub(crate) fn scope_config<F: std::future::Future>(
10 config: Arc<MailConfig>,
11 f: F,
12) -> impl std::future::Future<Output = F::Output> {
13 CURRENT_MAIL_CONFIG.scope(config, f)
14}
15
16static GLOBAL_MAIL_CONFIG: OnceLock<MailConfig> = OnceLock::new();
19
20pub fn set_global_config(config: MailConfig) -> Result<(), MailError> {
23 GLOBAL_MAIL_CONFIG
24 .set(config)
25 .map_err(|_| MailError::NotConfigured)
26}
27
28pub fn global_config() -> Option<&'static MailConfig> {
30 GLOBAL_MAIL_CONFIG.get()
31}
32
33#[cfg(feature = "queue")]
36tokio::task_local! {
37 pub(crate) static CURRENT_QUEUE: crate::queue_job::QueueRef;
38}
39
40#[cfg(feature = "queue")]
41pub(crate) fn scope_queue<F: std::future::Future>(
42 queue: crate::queue_job::QueueRef,
43 f: F,
44) -> impl std::future::Future<Output = F::Output> {
45 CURRENT_QUEUE.scope(queue, f)
46}
47
48pub struct Mail;
51
52impl Mail {
53 pub async fn send(mailable: impl Mailable, to: &str) -> Result<(), MailError> {
57 let config = CURRENT_MAIL_CONFIG
58 .try_with(|c| c.clone())
59 .map_err(|_| MailError::NotConfigured)?;
60 Self::dispatch(&mailable, to, &config).await
61 }
62
63 pub async fn send_with(
66 mailable: impl Mailable,
67 to: &str,
68 config: &MailConfig,
69 ) -> Result<(), MailError> {
70 Self::dispatch(&mailable, to, config).await
71 }
72
73 #[cfg(feature = "queue")]
78 pub async fn queue(mailable: impl Mailable, to: &str) -> Result<(), MailError> {
79 let queue = CURRENT_QUEUE
80 .try_with(|q| q.clone())
81 .map_err(|_| MailError::NotConfigured)?;
82
83 let job = crate::queue_job::SendMailJob {
84 to: to.to_string(),
85 subject: mailable.subject().to_string(),
86 body: mailable.body(),
87 html_body: mailable.html_body(),
88 };
89
90 queue
91 .dispatch(job)
92 .await
93 .map_err(|e| MailError::Http(e.to_string()))?;
94 Ok(())
95 }
96
97 #[cfg(feature = "queue")]
99 pub async fn later(
100 mailable: impl Mailable,
101 to: &str,
102 delay: std::time::Duration,
103 ) -> Result<(), MailError> {
104 let queue = CURRENT_QUEUE
105 .try_with(|q| q.clone())
106 .map_err(|_| MailError::NotConfigured)?;
107
108 let job = crate::queue_job::SendMailJob {
109 to: to.to_string(),
110 subject: mailable.subject().to_string(),
111 body: mailable.body(),
112 html_body: mailable.html_body(),
113 };
114
115 queue
116 .dispatch_in(job, delay)
117 .await
118 .map_err(|e| MailError::Http(e.to_string()))?;
119 Ok(())
120 }
121
122 async fn dispatch(
123 mailable: &dyn Mailable,
124 to: &str,
125 config: &MailConfig,
126 ) -> Result<(), MailError> {
127 match config.driver.as_str() {
128 "log" => crate::drivers::send_log(mailable, to, config).await,
129 #[cfg(feature = "smtp")]
130 "smtp" => crate::drivers::send_smtp(mailable, to, config).await,
131 #[cfg(feature = "postmark")]
132 "postmark" => crate::drivers::send_postmark(mailable, to, config).await,
133 #[cfg(feature = "resend")]
134 "resend" => crate::drivers::send_resend(mailable, to, config).await,
135 d => Err(MailError::UnknownDriver(d.to_string())),
136 }
137 }
138}