1pub mod sender;
10pub mod settings;
11pub mod task;
12pub mod template;
13
14pub use sender::EmailSender;
15pub use task::EmailSenderTask;
16pub use template::TemplateEngine;
17
18mod prelude;
19
20use crate::prelude::*;
21use cloudillo_core::settings::service::SettingsService;
22use serde::{Deserialize, Serialize};
23use std::sync::Arc;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct EmailMessage {
28 pub to: String,
29 pub subject: String,
30 pub text_body: String,
31 pub html_body: Option<String>,
32 #[serde(default)]
34 pub from_name_override: Option<String>,
35}
36
37#[derive(Debug, Clone)]
39pub struct EmailTaskParams {
40 pub to: String,
41 pub subject: Option<String>,
43 pub template_name: String,
44 pub template_vars: serde_json::Value,
45 pub lang: Option<String>,
47 pub custom_key: Option<String>,
48 pub from_name_override: Option<String>,
50}
51
52pub struct EmailModule {
54 pub settings_service: Arc<SettingsService>,
55 pub template_engine: Arc<TemplateEngine>,
56 pub sender: Arc<EmailSender>,
57}
58
59impl EmailModule {
60 pub fn new(settings_service: Arc<SettingsService>) -> ClResult<Self> {
61 let template_engine = Arc::new(TemplateEngine::new(settings_service.clone())?);
62 let sender = Arc::new(EmailSender::new(settings_service.clone())?);
63
64 Ok(Self { settings_service, template_engine, sender })
65 }
66
67 pub async fn schedule_email_task(
74 scheduler: &cloudillo_core::scheduler::Scheduler<App>,
75 settings_service: &cloudillo_core::settings::service::SettingsService,
76 tn_id: TnId,
77 params: EmailTaskParams,
78 ) -> ClResult<()> {
79 Self::schedule_email_task_with_key(scheduler, settings_service, tn_id, params).await
80 }
81
82 pub async fn schedule_email_task_with_key(
84 scheduler: &cloudillo_core::scheduler::Scheduler<App>,
85 settings_service: &cloudillo_core::settings::service::SettingsService,
86 tn_id: TnId,
87 params: EmailTaskParams,
88 ) -> ClResult<()> {
89 let max_retries = match settings_service.get(tn_id, "email.retry_attempts").await {
91 Ok(cloudillo_core::settings::SettingValue::Int(n)) => u16::try_from(n).unwrap_or(3),
92 _ => 3,
93 };
94
95 let retry_policy = cloudillo_core::scheduler::RetryPolicy::new((60, 3600), max_retries);
100
101 let task = EmailSenderTask::new(
102 tn_id,
103 params.to.clone(),
104 params.subject,
105 params.template_name,
106 params.template_vars,
107 params.lang,
108 params.from_name_override,
109 );
110 let task_key =
111 params.custom_key.unwrap_or_else(|| format!("email:{}:{}", tn_id.0, params.to));
112
113 scheduler
114 .task(std::sync::Arc::new(task))
115 .key(task_key)
116 .with_retry(retry_policy)
117 .schedule()
118 .await?;
119 info!("Email task scheduled for {} with {} retry attempts", params.to, max_retries);
120 Ok(())
121 }
122
123 pub async fn send_now(&self, tn_id: TnId, message: EmailMessage) -> ClResult<()> {
125 self.sender.send(tn_id, message).await
126 }
127}
128
129pub fn register_settings(
130 registry: &mut cloudillo_core::settings::SettingsRegistry,
131) -> ClResult<()> {
132 settings::register_settings(registry)
133}
134
135pub fn init(app: &App) -> ClResult<()> {
137 app.scheduler.register::<EmailSenderTask>()?;
138 Ok(())
139}
140
141pub async fn get_tenant_lang(settings: &SettingsService, tn_id: TnId) -> Option<String> {
145 match settings.get(tn_id, "profile.lang").await {
146 Ok(cloudillo_core::settings::SettingValue::String(lang)) => Some(lang),
147 _ => None,
148 }
149}
150
151