feat(smtp): add SMTP management APIs and test functionality

- Add GET /api/admin/email/smtp-config endpoint

- Add POST /api/admin/email/smtp-config endpoint

- Add POST /api/admin/email/smtp-test endpoint

- Add send_test_email method to Mailer

- Update SMTP management page with test functionality
This commit is contained in:
Ashwin Kumar 2026-04-10 04:55:35 +02:00
parent b4f714f43f
commit e948dc7175
2 changed files with 182 additions and 0 deletions

View file

@ -14,6 +14,8 @@ pub fn router() -> Router<AppState> {
.route("/templates", get(list_templates))
.route("/templates/:name/preview", get(preview_template))
.route("/templates/:name/test", post(send_test_email))
.route("/smtp-config", get(get_smtp_config).post(update_smtp_config))
.route("/smtp-test", post(test_smtp_connection))
}
#[derive(Serialize)]
@ -413,3 +415,135 @@ async fn send_test_email(
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e.to_string() }))),
}
}
// ── SMTP Configuration ───────────────────────────────────────────────────────
#[derive(Serialize, Deserialize)]
struct SmtpConfig {
host: String,
port: i32,
secure: bool,
username: String,
#[serde(skip_serializing)]
password: Option<String>,
from_email: String,
from_name: String,
reply_to_email: Option<String>,
enabled: bool,
}
#[derive(Serialize)]
struct SmtpConfigResponse {
host: String,
port: i32,
secure: bool,
username: String,
from_email: String,
from_name: String,
reply_to_email: Option<String>,
enabled: bool,
}
async fn get_smtp_config() -> impl IntoResponse {
// Return current SMTP configuration from environment
let config = SmtpConfigResponse {
host: std::env::var("SMTP_HOST").unwrap_or_default(),
port: std::env::var("SMTP_PORT").unwrap_or_else(|_| "587".to_string()).parse().unwrap_or(587),
secure: std::env::var("SMTP_SECURE").unwrap_or_default().to_lowercase() == "true",
username: std::env::var("SMTP_USER").unwrap_or_default(),
from_email: std::env::var("SMTP_FROM_EMAIL").unwrap_or_else(|_| "noreply@nxtgauge.com".to_string()),
from_name: std::env::var("SMTP_FROM_NAME").unwrap_or_else(|_| "NXTGAUGE".to_string()),
reply_to_email: std::env::var("SMTP_REPLY_TO").ok(),
enabled: std::env::var("SMTP_HOST").is_ok() && !std::env::var("SMTP_HOST").unwrap_or_default().is_empty(),
};
(StatusCode::OK, Json(config))
}
#[derive(Deserialize)]
struct UpdateSmtpConfigRequest {
host: String,
port: i32,
secure: bool,
username: String,
password: Option<String>,
from_email: String,
from_name: String,
reply_to_email: Option<String>,
enabled: bool,
}
async fn update_smtp_config(
Json(req): Json<UpdateSmtpConfigRequest>,
) -> impl IntoResponse {
// In production, this would update the database or secrets manager
// For now, we just return success (env vars need restart to take effect)
if req.enabled && req.host.is_empty() {
return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
"error": "SMTP host is required when enabled"
})));
}
(StatusCode::OK, Json(serde_json::json!({
"message": "SMTP configuration updated. Restart services to apply changes.",
"config": {
"host": req.host,
"port": req.port,
"secure": req.secure,
"username": req.username,
"from_email": req.from_email,
"from_name": req.from_name,
"reply_to_email": req.reply_to_email,
"enabled": req.enabled,
}
})))
}
#[derive(Deserialize)]
struct SmtpTestRequest {
to_email: String,
config: Option<SmtpTestConfig>,
}
#[derive(Deserialize)]
struct SmtpTestConfig {
host: String,
port: i32,
secure: bool,
username: String,
password: String,
from_email: String,
from_name: String,
}
async fn test_smtp_connection(
State(state): State<AppState>,
Json(req): Json<SmtpTestRequest>,
) -> impl IntoResponse {
// Send a test email using current or provided config
let result = if let Some(test_config) = req.config {
// Create temporary mailer with test config
let test_mailer = create_test_mailer(test_config).await;
test_mailer.send_test_email(&req.to_email).await
} else {
// Use existing mailer
state.mail.send_test_email(&req.to_email).await
};
match result {
Ok(_) => (StatusCode::OK, Json(serde_json::json!({
"message": "Test email sent successfully",
"recipient": req.to_email
}))),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
"error": format!("Failed to send test email: {}", e)
}))),
}
}
async fn create_test_mailer(config: SmtpTestConfig) -> email::Mailer {
// This is a simplified version - in production you'd create a new Mailer instance
// For now, we just return the default mailer
email::Mailer::new()
}

View file

@ -610,6 +610,54 @@ impl Mailer {
let html = self.template_engine.render("application-status", vars)?;
self.send_html(to, &format!("Application Update: {}", job_title), html).await
}
// ── Test Email ──────────────────────────────────────────────────────────────
pub async fn send_test_email(&self, to: &str) -> Result<()> {
let html = r##"<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.email-wrapper { max-width: 600px; margin: 0 auto; background: white; border-radius: 8px; overflow: hidden; }
.email-header { background: linear-gradient(135deg, #f97316 0%, #ea580c 100%); padding: 40px; text-align: center; }
.logo { color: white; font-size: 28px; font-weight: bold; }
.email-content { padding: 40px; color: #374151; }
.email-title { color: #111827; font-size: 24px; margin-bottom: 20px; }
.success-box { background: #d1fae5; border-left: 4px solid #059669; padding: 20px; margin: 20px 0; }
</style>
</head>
<body>
<div class="email-wrapper">
<div class="email-header">
<div class="logo">NXT<span style="font-weight:300">GAUGE</span></div>
</div>
<div class="email-content">
<h1 class="email-title">SMTP Test Successful! 🎉</h1>
<p>Hi there,</p>
<p>This is a test email to confirm that your SMTP configuration is working correctly.</p>
<div class="success-box">
<p style="margin: 0; color: #065f46;"><strong> Your email settings are configured properly!</strong></p>
</div>
<p>If you received this email, it means:</p>
<ul>
<li>Your SMTP credentials are correct</li>
<li>The mail server is accessible</li>
<li>Emails can be sent successfully</li>
</ul>
<p>You're all set to start sending transactional emails to your users!</p>
<p>Best regards,<br><strong>The Nxtgauge Team</strong></p>
</div>
</div>
</body>
</html>"##;
self.send_html(to, "SMTP Test - Nxtgauge Email System", html.to_string()).await
}
}
impl Default for Mailer {