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