use emailks::{ config::AppConfig, email::EmailSender, pb::email::v1::email_service_server::EmailServiceServer, queue::EmailQueue, server::EmailServiceImpl, }; use tonic::transport::Server; use tracing::{error, info}; const DEFAULT_QUEUE_CAPACITY: usize = 1_000; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()), ) .init(); let config = AppConfig::from_env()?; info!(?config.smtp.host, port = config.smtp.port, "smtp config loaded"); let sender = EmailSender::new(config.smtp)?; let (queue, worker) = match config.queue_capacity { // `Some(0)` explicitly opts into an unbounded queue (mainly for testing). Some(0) => { info!("creating unbounded queue by explicit configuration"); EmailQueue::unbounded() } Some(cap) => { info!(capacity = cap, "creating bounded queue"); EmailQueue::bounded(cap) } None => { info!( capacity = DEFAULT_QUEUE_CAPACITY, "creating bounded queue with default capacity" ); EmailQueue::bounded(DEFAULT_QUEUE_CAPACITY) } }; let store = queue.status_store().clone(); let worker_handle = worker.spawn(move |job| { let s = sender.clone(); async move { s.send_job(&job).await } }); let addr = config.listen_addr; let svc = EmailServiceImpl::new(queue, store); let (health, health_svc) = tonic_health::server::health_reporter(); health .set_serving::>() .await; info!(%addr, "gRPC server starting"); Server::builder() .add_service(health_svc) .add_service(EmailServiceServer::new(svc)) .serve_with_shutdown(addr, shutdown_signal()) .await?; info!("server stopped"); if let Err(e) = worker_handle.await { tracing::error!(error = %e, "worker task panicked"); } Ok(()) } async fn shutdown_signal() { match tokio::signal::ctrl_c().await { Ok(()) => info!("shutdown signal received, draining..."), Err(err) => error!(%err, "failed to install CTRL+C handler, shutting down"), } }