swadhinbiswas

[email protected]
2026-07-25 12:46:36 +0600 committed NaN years ago
71b8e14
commit 71b8e14
2 files changed
+89 -0
▼crates/justapi-core/src/mail/queue.rs
+42-0
1
+use tokio::sync::mpsc;
2
+
3
+use super::{EmailMessage, smtp::SmtpSender};
4
+
5
+/// Background email delivery via a tokio mpsc channel.
6
+pub struct EmailQueue {
7
+ sender: mpsc::UnboundedSender<QueuedEmail>,
8
+}
9
+
10
+struct QueuedEmail {
11
+ msg: EmailMessage,
12
+}
13
+
14
+impl EmailQueue {
15
+ /// Spawn a background worker that processes emails from the queue.
16
+ /// The worker receives an SmtpSender.
17
+ pub fn spawn(smtp: SmtpSender) -> Self {
18
+ let (tx, mut rx) = mpsc::unbounded_channel::<QueuedEmail>();
19
+
20
+ tokio::spawn(async move {
21
+ while let Some(queued) = rx.recv().await {
22
+ if let Err(e) = smtp.send(&queued.msg).await {
23
+ tracing::error!("Failed to send queued email: {}", e);
24
+ }
25
+ }
26
+ });
27
+
28
+ Self { sender: tx }
29
+ }
30
+
31
+ /// Enqueue an email for background delivery.
32
+ pub fn enqueue(&self, msg: EmailMessage) -> Result<(), anyhow::Error> {
33
+ self.sender
34
+ .send(QueuedEmail { msg })
35
+ .map_err(|_| anyhow::anyhow!("email queue is closed"))
36
+ }
37
+
38
+ /// Enqueue and return immediately. Errors are logged by the worker.
39
+ pub fn enqueue_lossy(&self, msg: EmailMessage) {
40
+ let _ = self.enqueue(msg);
41
+ }
42
+}
0
43
▼crates/justapi-core/src/mail/template.rs
+47-0
1
+use std::collections::HashMap;
2
+use std::sync::Arc;
3
+
4
+use minijinja::Environment;
5
+use tokio::sync::RwLock;
6
+
7
+/// Renders email templates using MiniJinja.
8
+pub struct EmailTemplates {
9
+ env: Arc<RwLock<Environment<'static>>>,
10
+}
11
+
12
+impl std::fmt::Debug for EmailTemplates {
13
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14
+ f.debug_struct("EmailTemplates").finish()
15
+ }
16
+}
17
+
18
+impl EmailTemplates {
19
+ pub fn new() -> Self {
20
+ Self { env: Arc::new(RwLock::new(Environment::new())) }
21
+ }
22
+
23
+ /// Add a template from a string.
24
+ pub async fn add_template(&self, name: &str, template: &str) -> Result<(), anyhow::Error> {
25
+ let mut env = self.env.write().await;
26
+ env.add_template_owned(name.to_string(), template.to_string())?;
27
+ Ok(())
28
+ }
29
+
30
+ /// Render a named template with the given context.
31
+ pub async fn render(
32
+ &self,
33
+ name: &str,
34
+ ctx: &HashMap<String, serde_json::Value>,
35
+ ) -> Result<String, anyhow::Error> {
36
+ let env = self.env.read().await;
37
+ let tmpl = env.get_template(name)?;
38
+ let result = tmpl.render(ctx)?;
39
+ Ok(result)
40
+ }
41
+}
42
+
43
+impl Default for EmailTemplates {
44
+ fn default() -> Self {
45
+ Self::new()
46
+ }
47
+}
0
48