Skip to main content

cloudillo_admin/
email.rs

1//! Admin email testing handlers
2
3use axum::{
4	extract::State,
5	http::StatusCode,
6	response::{IntoResponse, Response},
7	Json,
8};
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12use std::sync::Arc;
13
14use cloudillo_core::extract::Auth;
15use cloudillo_email::{EmailMessage, EmailModule};
16use cloudillo_types::types::{ApiResponse, ErrorResponse};
17
18use crate::prelude::*;
19
20/// Request body for test email endpoint
21#[derive(Debug, Clone, Deserialize)]
22pub struct TestEmailRequest {
23	/// Target email address to send the test email to
24	pub to: String,
25}
26
27/// Response body for test email endpoint
28#[skip_serializing_none]
29#[derive(Debug, Clone, Serialize)]
30pub struct TestEmailResponse {
31	/// Whether the test email was sent successfully
32	pub success: bool,
33	/// Human-readable status message
34	pub message: String,
35	/// Error details if the send failed
36	pub error: Option<String>,
37}
38
39/// POST /api/admin/email/test - Send a test email to verify email configuration
40///
41/// This endpoint allows administrators to verify that email settings are properly
42/// configured by sending a test email to a specified address.
43#[axum::debug_handler]
44pub async fn send_test_email(
45	State(app): State<App>,
46	Auth(auth_ctx): Auth,
47	Json(request): Json<TestEmailRequest>,
48) -> ClResult<Response> {
49	let tn_id = auth_ctx.tn_id;
50
51	info!(
52		tn_id = ?tn_id,
53		to = %request.to,
54		"POST /api/admin/email/test - Sending test email"
55	);
56
57	// Validate email format (basic check)
58	if !request.to.contains('@') || !request.to.contains('.') {
59		return Ok((
60			StatusCode::BAD_REQUEST,
61			Json(ErrorResponse::new(
62				"INVALID_EMAIL_FORMAT".to_string(),
63				"Invalid email address format. Email address must contain @ and .".to_string(),
64			)),
65		)
66			.into_response());
67	}
68
69	// Check if email is enabled
70	let email_enabled = app.settings.get_bool(tn_id, "email.enabled").await.unwrap_or(false);
71	if !email_enabled {
72		return Ok((
73			StatusCode::PRECONDITION_FAILED,
74			Json(ErrorResponse::new(
75				"EMAIL_DISABLED".to_string(),
76				"Email sending is disabled. Enable email.enabled setting to send emails."
77					.to_string(),
78			)),
79		)
80			.into_response());
81	}
82
83	// Check if SMTP host is configured
84	let smtp_host = app.settings.get_string_opt(tn_id, "email.smtp.host").await.unwrap_or(None);
85	if smtp_host.is_none() || smtp_host.as_ref().is_some_and(|h| h.is_empty()) {
86		return Ok((
87			StatusCode::PRECONDITION_FAILED,
88			Json(ErrorResponse::new(
89				"SMTP_NOT_CONFIGURED".to_string(),
90				"SMTP host not configured. Configure email.smtp.host setting.".to_string(),
91			)),
92		)
93			.into_response());
94	}
95
96	// Get base_id_tag for sender name
97	let base_id_tag = app.opts.base_id_tag.as_ref().map(|s| s.as_ref()).unwrap_or("cloudillo");
98
99	// Create test email message
100	let message = EmailMessage {
101		to: request.to.clone(),
102		subject: format!("Test Email from Cloudillo ({})", base_id_tag),
103		text_body: format!(
104			"This is a test email from your Cloudillo instance ({}).\n\n\
105			If you received this email, your email configuration is working correctly.\n\n\
106			-- Cloudillo",
107			base_id_tag
108		),
109		html_body: Some(format!(
110			"<html><body>\
111			<h2>Test Email from Cloudillo</h2>\
112			<p>This is a test email from your Cloudillo instance (<strong>{}</strong>).</p>\
113			<p>If you received this email, your email configuration is working correctly.</p>\
114			<hr>\
115			<p style=\"color: #666;\">-- Cloudillo</p>\
116			</body></html>",
117			base_id_tag
118		)),
119		from_name_override: Some(format!("Cloudillo | {}", base_id_tag.to_uppercase())),
120	};
121
122	// Send immediately for direct feedback
123	match app.ext::<Arc<EmailModule>>()?.send_now(tn_id, message).await {
124		Ok(()) => {
125			info!(
126				tn_id = ?tn_id,
127				to = %request.to,
128				"Test email sent successfully"
129			);
130			Ok((
131				StatusCode::OK,
132				Json(ApiResponse::new(TestEmailResponse {
133					success: true,
134					message: format!("Test email sent to {}", request.to),
135					error: None,
136				})),
137			)
138				.into_response())
139		}
140		Err(e) => {
141			warn!(
142				tn_id = ?tn_id,
143				to = %request.to,
144				error = %e,
145				"Failed to send test email"
146			);
147			Ok((
148				StatusCode::SERVICE_UNAVAILABLE,
149				Json(ErrorResponse::new(
150					"SMTP_SEND_FAILED".to_string(),
151					format!("Failed to send test email: {}", e),
152				)),
153			)
154				.into_response())
155		}
156	}
157}
158
159// vim: ts=4