use std::time::Duration; use tracing::{debug, info}; use crate::error::EmailError; use crate::events::{ CHANNEL_BATCH, CHANNEL_RENDER, CHANNEL_SEND, EmailFailed, RenderAndSendEmailRequest, SendBatchEmailRequest, SendEmailRequest, now_ms, }; use crate::queue::{SubscribeHandle, TypedMessageMeta}; use crate::service::EmailService; pub struct EmailSubscribers { single: Option>, batch: Option>, render: Option>, } impl EmailSubscribers { pub fn empty() -> Self { Self { single: None, batch: None, render: None, } } pub async fn shutdown(self, timeout: Duration) -> Result<(), EmailError> { let s = self.single; let b = self.batch; let r = self.render; let mut last_err: Option = None; if let Some(h) = s { if let Err(e) = h.shutdown(timeout).await { last_err = Some(e); } } if let Some(h) = b { if let Err(e) = h.shutdown(timeout).await { last_err = Some(e); } } if let Some(h) = r { if let Err(e) = h.shutdown(timeout).await { last_err = Some(e); } } match last_err { Some(e) => Err(e), None => Ok(()), } } } pub async fn start_subscribers(service: EmailService) -> Result { let queue = match service.queue() { Some(q) if q.enabled() => q, _ => { info!("email queue disabled; skipping pubsub subscribers"); return Ok(EmailSubscribers::empty()); } }; let group = service.config().smtp_host().to_string(); info!( group = %group, prefix = %queue.config().channel_prefix, "subscribing to email channels" ); let single = queue .subscribe(CHANNEL_SEND, &group, { let svc = service.clone(); move |req: SendEmailRequest, _meta: TypedMessageMeta| { let svc = svc.clone(); async move { let recipient_count = req.to.len() + req.cc.len() + req.bcc.len(); let request_id = req.request_id; let metadata = req.metadata.clone(); debug!( request_id = %request_id, recipients = recipient_count, channel = CHANNEL_SEND, "handler invoked" ); match svc.send(req).await { Ok(sent) => { debug!( request_id = %sent.request_id, smtp_message_id = %sent.smtp_message_id, "smtp send ok" ); if let Err(err) = svc.publish_sent(&sent).await { tracing::warn!(error = %err, "publish EmailSent failed"); } } Err(err) => { let failed = EmailFailed { request_id, error: err.to_string(), recipient_count, failed_at_ms: now_ms(), metadata, }; tracing::error!( request_id = %request_id, error = %err, "email send failed" ); if let Err(pub_err) = svc.publish_failed(&failed).await { tracing::warn!(error = %pub_err, "publish EmailFailed failed"); } } } Ok(()) } } }) .await?; info!(channel = CHANNEL_SEND, "subscribed channel"); let batch = queue .subscribe(CHANNEL_BATCH, &group, { let svc = service.clone(); move |req: SendBatchEmailRequest, _meta: TypedMessageMeta| { let svc = svc.clone(); async move { let total = req.recipients.len(); let request_id = req.request_id; debug!( request_id = %request_id, total, channel = CHANNEL_BATCH, "batch handler invoked" ); match svc.send_batch(req).await { Ok(_) => { tracing::debug!(request_id = %request_id, total, "batch completed"); } Err(err) => { let failed = EmailFailed { request_id, error: err.to_string(), recipient_count: total, failed_at_ms: now_ms(), metadata: Default::default(), }; tracing::error!( request_id = %request_id, error = %err, "batch failed" ); if let Err(pub_err) = svc.publish_failed(&failed).await { tracing::warn!(error = %pub_err, "publish EmailFailed failed"); } } } Ok(()) } } }) .await?; info!(channel = CHANNEL_BATCH, "subscribed channel"); let render = queue .subscribe(CHANNEL_RENDER, &group, { let svc = service.clone(); move |req: RenderAndSendEmailRequest, _meta: TypedMessageMeta| { let svc = svc.clone(); async move { let request_id = req.request_id; let recipient_count = req.to.len() + req.cc.len() + req.bcc.len(); let metadata = req.metadata.clone(); debug!( request_id = %request_id, template = %req.template, recipients = recipient_count, channel = CHANNEL_RENDER, "render handler invoked" ); match svc.render_and_send(req).await { Ok(sent) => { debug!( request_id = %sent.request_id, smtp_message_id = %sent.smtp_message_id, "render+send ok" ); if let Err(err) = svc.publish_sent(&sent).await { tracing::warn!(error = %err, "publish EmailSent failed"); } } Err(err) => { let failed = EmailFailed { request_id, error: err.to_string(), recipient_count, failed_at_ms: now_ms(), metadata, }; tracing::error!( request_id = %request_id, error = %err, "render+send failed" ); if let Err(pub_err) = svc.publish_failed(&failed).await { tracing::warn!(error = %pub_err, "publish EmailFailed failed"); } } } Ok(()) } } }) .await?; info!(channel = CHANNEL_RENDER, "subscribed channel"); Ok(EmailSubscribers { single: Some(single), batch: Some(batch), render: Some(render), }) }