nxtgauge-backend-rust/crates/email/src/lib.rs

668 lines
32 KiB
Rust
Raw Normal View History

use anyhow::Result;
use lettre::{
message::{header::ContentType, Mailbox, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
};
use std::collections::HashMap;
use std::env;
// ── Template Engine ───────────────────────────────────────────────────────────
pub struct TemplateEngine;
impl TemplateEngine {
pub fn new() -> Self {
Self
}
pub fn render(&self, template_name: &str, vars: HashMap<&str, &str>) -> Result<String> {
let base = include_str!("../templates/base.html");
let content = self.load_template(template_name)?;
// Replace content in base template
let mut html = base.replace("{{content}}", content);
// Replace all variables
for (key, value) in vars {
html = html.replace(&format!("{{{{{}}}}}", key), value);
}
Ok(html)
}
fn load_template(&self, name: &str) -> Result<&str> {
match name {
// Auth & Account
"welcome" => Ok(include_str!("../templates/welcome.html")),
"verify-email" => Ok(include_str!("../templates/verify-email.html")),
"password-reset" => Ok(include_str!("../templates/password-reset.html")),
"password-changed" => Ok(include_str!("../templates/password-changed.html")),
"account-suspended" => Ok(include_str!("../templates/account-suspended.html")),
// Onboarding & Verification
"onboarding-submitted" => Ok(include_str!("../templates/onboarding-submitted.html")),
"profile-verified" => Ok(include_str!("../templates/profile-verified.html")),
"profile-rejected" => Ok(include_str!("../templates/profile-rejected.html")),
"documents-requested" => Ok(include_str!("../templates/documents-requested.html")),
// Jobs
"job-pending" => Ok(include_str!("../templates/job-pending.html")),
"job-approved" => Ok(include_str!("../templates/job-approved.html")),
"job-rejected" => Ok(include_str!("../templates/job-rejected.html")),
"job-expired" => Ok(include_str!("../templates/job-expired.html")),
// Applications
"application-received" => Ok(include_str!("../templates/application-received.html")),
"application-status" => Ok(include_str!("../templates/application-status.html")),
// Requirements
"requirement-pending" => Ok(include_str!("../templates/requirement-pending.html")),
"requirement-approved" => Ok(include_str!("../templates/requirement-approved.html")),
"requirement-expired" => Ok(include_str!("../templates/requirement-expired.html")),
// Leads
"new-lead-request" => Ok(include_str!("../templates/new-lead-request.html")),
"lead-request-sent" => Ok(include_str!("../templates/lead-request-sent.html")),
"lead-request-accepted" => Ok(include_str!("../templates/lead-request-accepted.html")),
"lead-request-rejected" => Ok(include_str!("../templates/lead-request-rejected.html")),
"lead-expired" => Ok(include_str!("../templates/lead-expired.html")),
// Payments
"payment-success" => Ok(include_str!("../templates/payment-success.html")),
"invoice-generated" => Ok(include_str!("../templates/invoice-generated.html")),
"manual-credit" => Ok(include_str!("../templates/manual-credit.html")),
"credit-usage" => Ok(include_str!("../templates/credit-usage.html")),
"low-credit-balance" => Ok(include_str!("../templates/low-credit-balance.html")),
// Security
"new-device-login" => Ok(include_str!("../templates/new-device-login.html")),
// Support
"support-ticket-created" => Ok(include_str!("../templates/support-ticket-created.html")),
"support-ticket-replied" => Ok(include_str!("../templates/support-ticket-replied.html")),
"support-ticket-resolved" => Ok(include_str!("../templates/support-ticket-resolved.html")),
// Marketplace
"new-matched-lead" => Ok(include_str!("../templates/new-matched-lead.html")),
// Policy
"policy-update" => Ok(include_str!("../templates/policy-update.html")),
_ => Err(anyhow::anyhow!("Template not found: {}", name)),
}
}
}
impl Default for TemplateEngine {
fn default() -> Self {
Self::new()
}
}
// ── Mailer ────────────────────────────────────────────────────────────────────
pub struct Mailer {
transport: Option<AsyncSmtpTransport<Tokio1Executor>>,
from_email: String,
from_name: String,
template_engine: TemplateEngine,
}
impl Mailer {
pub fn new() -> Self {
let smtp_host = env::var("SMTP_HOST").ok();
let smtp_user = env::var("SMTP_USER").ok();
let smtp_pass = env::var("SMTP_PASS").ok();
let smtp_port: u16 = env::var("SMTP_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(587);
let from_email = env::var("SMTP_FROM_EMAIL")
.unwrap_or_else(|_| "noreply@nxtgauge.com".to_string());
let from_name = env::var("SMTP_FROM_NAME")
.unwrap_or_else(|_| "NXTGAUGE".to_string());
let transport = match (smtp_host, smtp_user, smtp_pass) {
(Some(host), Some(user), Some(pass)) => {
let creds = Credentials::new(user, pass);
match AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&host) {
Ok(builder) => {
let t = builder.port(smtp_port).credentials(creds).build();
tracing::info!("SMTP transport configured (host={} port={})", host, smtp_port);
Some(t)
}
Err(e) => {
tracing::warn!("SMTP transport init failed: {} — emails disabled", e);
None
}
}
}
_ => {
tracing::warn!("SMTP not configured — emails disabled");
None
}
};
Self {
transport,
from_email,
from_name,
template_engine: TemplateEngine::new(),
}
}
async fn send_html(&self, to: &str, subject: &str, html_body: String) -> Result<()> {
let Some(transport) = &self.transport else {
tracing::debug!("SMTP disabled — skipping email to {}", to);
return Ok(());
};
let from: Mailbox = format!("{} <{}>", self.from_name, self.from_email).parse()?;
let to: Mailbox = to.parse()?;
let email = Message::builder()
.from(from)
.to(to)
.subject(subject)
.header(ContentType::TEXT_HTML)
.body(html_body)?;
transport.send(email).await?;
Ok(())
}
// ── Auth & Account ────────────────────────────────────────────────────────
pub async fn send_verification_email(&self, to: &str, name: &str, otp: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", name),
("otp_code", otp),
]);
let html = self.template_engine.render("verify-email", vars)?;
self.send_html(to, "Verify Your Email Address", html).await
}
pub async fn send_password_reset_email(&self, to: &str, name: &str, token: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let reset_url = format!("{}/reset-password?token={}", frontend_url, token);
let vars = HashMap::from([
("first_name", name),
("reset_url", &reset_url),
]);
let html = self.template_engine.render("password-reset", vars)?;
self.send_html(to, "Reset Your Password", html).await
}
pub async fn send_welcome_email(&self, to: &str, name: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let dashboard_url = format!("{}/dashboard", frontend_url);
let vars = HashMap::from([
("first_name", name),
("dashboard_url", &dashboard_url),
]);
let html = self.template_engine.render("welcome", vars)?;
self.send_html(to, "Welcome to Nxtgauge!", html).await
}
// ── Profile Verification ───────────────────────────────────────────────────
pub async fn send_profile_verified_email(&self, to: &str, name: &str, role_name: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("role_name", role_name),
("verified_at", &now),
("dashboard_url", &frontend_url),
]);
let html = self.template_engine.render("profile-verified", vars)?;
self.send_html(to, "Your Profile is Verified!", html).await
}
pub async fn send_profile_rejected_email(&self, to: &str, name: &str, role_name: &str, reason: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let profile_url = format!("{}/dashboard/profile", frontend_url);
let vars = HashMap::from([
("first_name", name),
("role_name", role_name),
("rejection_reason", reason),
("profile_url", &profile_url),
]);
let html = self.template_engine.render("profile-rejected", vars)?;
self.send_html(to, "Profile Verification Update", html).await
}
// ── Jobs ───────────────────────────────────────────────────────────────────
pub async fn send_job_pending_email(&self, to: &str, company_name: &str, job_title: &str) -> Result<()> {
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", company_name),
("job_title", job_title),
("posted_at", &now),
]);
let html = self.template_engine.render("job-pending", vars)?;
self.send_html(to, "Job Posted Successfully", html).await
}
pub async fn send_job_approved_email(&self, to: &str, company_name: &str, job_title: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let expires = (chrono::Local::now() + chrono::Duration::days(30)).format("%B %d, %Y").to_string();
let job_url = format!("{}/dashboard/jobs", frontend_url);
let vars = HashMap::from([
("first_name", company_name),
("job_title", job_title),
("approved_at", &now),
("expires_at", &expires),
("job_url", &job_url),
]);
let html = self.template_engine.render("job-approved", vars)?;
self.send_html(to, "Your Job is Now Live!", html).await
}
pub async fn send_job_rejected_email(&self, to: &str, company_name: &str, job_title: &str, reason: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", company_name),
("job_title", job_title),
("rejection_reason", reason),
]);
let html = self.template_engine.render("job-rejected", vars)?;
self.send_html(to, "Job Posting Needs Updates", html).await
}
pub async fn send_application_received_email(&self, to: &str, company_name: &str, job_title: &str, applicant_name: &str, total_apps: i64) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let apps_url = format!("{}/dashboard/jobs", frontend_url);
let total_str = total_apps.to_string();
let vars = HashMap::from([
("first_name", company_name),
("job_title", job_title),
("applicant_name", applicant_name),
("total_applications", &total_str),
("applications_url", &apps_url),
]);
let html = self.template_engine.render("application-received", vars)?;
self.send_html(to, &format!("New Application: {}", job_title), html).await
}
// ── Leads ──────────────────────────────────────────────────────────────────
pub async fn send_lead_request_sent_email(&self, to: &str, name: &str, requirement_title: &str, location: &str, reserved: i32) -> Result<()> {
let reserved_str = reserved.to_string();
let vars = HashMap::from([
("first_name", name),
("requirement_title", requirement_title),
("location", location),
("tracecoins_reserved", &reserved_str),
]);
let html = self.template_engine.render("lead-request-sent", vars)?;
self.send_html(to, "Lead Request Sent", html).await
}
pub async fn send_lead_request_accepted_email(&self, to: &str, professional_name: &str, customer_name: &str, requirement_title: &str, deducted: i32) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let lead_url = format!("{}/dashboard/leads/accepted", frontend_url);
let deducted_str = deducted.to_string();
let vars = HashMap::from([
("first_name", professional_name),
("customer_name", customer_name),
("requirement_title", requirement_title),
("tracecoins_deducted", &deducted_str),
("accepted_at", &now),
("lead_url", &lead_url),
]);
let html = self.template_engine.render("lead-request-accepted", vars)?;
self.send_html(to, "Lead Request Accepted!", html).await
}
pub async fn send_new_lead_request_email(&self, to: &str, customer_name: &str, requirement_title: &str, professional_name: &str, profession: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y at %I:%M %p").to_string();
let req_url = format!("{}/dashboard/requirements", frontend_url);
let vars = HashMap::from([
("first_name", customer_name),
("requirement_title", requirement_title),
("professional_name", professional_name),
("profession_type", profession),
("requested_at", &now),
("requirement_url", &req_url),
]);
let html = self.template_engine.render("new-lead-request", vars)?;
self.send_html(to, &format!("New Request: {}", requirement_title), html).await
}
// ── Payments ───────────────────────────────────────────────────────────────
pub async fn send_payment_success_email(&self, to: &str, name: &str, package_name: &str, amount: i32, tracecoins: i32, transaction_id: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let invoice_url = format!("{}/dashboard/wallet/invoices", frontend_url);
let wallet_url = format!("{}/dashboard/wallet", frontend_url);
let amount_str = format!("{:.2}", amount as f32 / 100.0);
let tc_str = tracecoins.to_string();
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("package_name", package_name),
("amount_paid", &amount_str),
("tracecoins_amount", &tc_str),
("transaction_id", transaction_id),
("payment_date", &now),
("invoice_url", &invoice_url),
("wallet_url", &wallet_url),
]);
let html = self.template_engine.render("payment-success", vars)?;
self.send_html(to, "Payment Successful!", html).await
}
// ── Account ─────────────────────────────────────────────────────────────────
pub async fn send_account_suspended_email(&self, to: &str, name: &str, reason: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", name),
("suspension_reason", reason),
]);
let html = self.template_engine.render("account-suspended", vars)?;
self.send_html(to, "Account Suspended", html).await
}
// ── Cron / Expiry ───────────────────────────────────────────────────────────
pub async fn send_lead_expired_email(&self, to: &str, name: &str, tracecoins: i32) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let tc_str = tracecoins.to_string();
let marketplace_url = format!("{}/dashboard/marketplace", frontend_url);
let vars = HashMap::from([
("first_name", name),
("tracecoins_returned", &tc_str),
("marketplace_url", &marketplace_url),
]);
let html = self.template_engine.render("lead-expired", vars)?;
self.send_html(to, "Lead Request Expired", html).await
}
pub async fn send_requirement_expired_email(&self, to: &str, name: &str, title: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", name),
("requirement_title", title),
]);
let html = self.template_engine.render("requirement-expired", vars)?;
self.send_html(to, "Requirement Expired", html).await
}
pub async fn send_job_expired_email(&self, to: &str, company_name: &str, title: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", company_name),
("job_title", title),
]);
let html = self.template_engine.render("job-expired", vars)?;
self.send_html(to, "Job Posting Expired", html).await
}
// ── Missing Methods Added ───────────────────────────────────────────────────
pub async fn send_password_changed_email(&self, to: &str, name: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y at %I:%M %p").to_string();
let security_url = format!("{}/dashboard/settings", frontend_url);
let vars = HashMap::from([
("first_name", name),
("email", to),
("changed_at", &now),
("security_url", &security_url),
]);
let html = self.template_engine.render("password-changed", vars)?;
self.send_html(to, "Password Changed Successfully", html).await
}
pub async fn send_account_deleted_email(&self, to: &str, name: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", name),
]);
// Use account-suspended template as base or create a simple message
self.send_html(
to,
"Account Deleted",
format!("<html><body><h1>Account Deleted</h1><p>Hi {},</p><p>Your account has been deleted.</p></body></html>", name)
).await
}
pub async fn send_approval_approved_email(&self, to: &str, name: &str, role: &str) -> Result<()> {
self.send_profile_verified_email(to, name, role).await
}
pub async fn send_approval_rejected_email(&self, to: &str, name: &str, role: &str, reason: &str) -> Result<()> {
self.send_profile_rejected_email(to, name, role, reason).await
}
pub async fn send_job_submitted_email(&self, to: &str, company_name: &str, job_title: &str) -> Result<()> {
self.send_job_pending_email(to, company_name, job_title).await
}
pub async fn send_new_application_email(&self, to: &str, company_name: &str, job_title: &str, applicant_name: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let apps_url = format!("{}/dashboard/jobs", frontend_url);
let vars = HashMap::from([
("first_name", company_name),
("job_title", job_title),
("applicant_name", applicant_name),
("total_applications", "1"),
("applications_url", &apps_url),
]);
let html = self.template_engine.render("application-received", vars)?;
self.send_html(to, &format!("New Application: {}", job_title), html).await
}
pub async fn send_requirement_submitted_email(&self, to: &str, name: &str, title: &str) -> Result<()> {
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("requirement_title", title),
("profession_type", "Service"),
("posted_at", &now),
]);
let html = self.template_engine.render("requirement-pending", vars)?;
self.send_html(to, "Requirement Submitted Successfully", html).await
}
pub async fn send_lead_accepted_customer_email(&self, to: &str, customer_name: &str, professional_name: &str, professional_email: &str, professional_phone: &str) -> Result<()> {
let vars = HashMap::from([
("first_name", customer_name),
("professional_name", professional_name),
("professional_email", professional_email),
("professional_phone", professional_phone),
]);
// Use a simple template or new-lead-request as base
self.send_html(
to,
"Professional Contact Details",
format!("<html><body><h1>Professional Contact</h1><p>Hi {},</p><p>You accepted {}'s request.</p><p>Email: {}</p><p>Phone: {}</p></body></html>",
customer_name, professional_name, professional_email, professional_phone)
).await
}
pub async fn send_manual_credit_email(&self, to: &str, name: &str, amount: i32, reason: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let wallet_url = format!("{}/dashboard/wallet", frontend_url);
let amount_str = amount.to_string();
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("tracecoins_amount", &amount_str),
("credited_at", &now),
("reason", reason),
("wallet_url", &wallet_url),
]);
let html = self.template_engine.render("manual-credit", vars)?;
self.send_html(to, "Tracecoins Credited to Your Account", html).await
}
// ── Support Tickets ─────────────────────────────────────────────────────────
pub async fn send_support_ticket_created_email(&self, to: &str, name: &str, ticket_id: &str, subject: &str, category: &str, priority: &str, response_time: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let ticket_url = format!("{}/dashboard/support/{}", frontend_url, ticket_id);
let vars = HashMap::from([
("first_name", name),
("ticket_id", ticket_id),
("subject", subject),
("category", category),
("priority", priority),
("response_time", response_time),
("ticket_url", &ticket_url),
]);
let html = self.template_engine.render("support-ticket-created", vars)?;
self.send_html(to, "Support Ticket Received", html).await
}
pub async fn send_support_ticket_replied_email(&self, to: &str, name: &str, subject: &str, message: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let ticket_url = format!("{}/dashboard/support", frontend_url);
let vars = HashMap::from([
("first_name", name),
("subject", subject),
("latest_message", message),
("support_agent_name", "Nxtgauge Support Team"),
("ticket_url", &ticket_url),
]);
let html = self.template_engine.render("support-ticket-replied", vars)?;
self.send_html(to, "New Response on Your Ticket", html).await
}
pub async fn send_support_ticket_resolved_email(&self, to: &str, name: &str, subject: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("subject", subject),
("resolved_at", &now),
("support_agent_name", "Nxtgauge Support Team"),
]);
let html = self.template_engine.render("support-ticket-resolved", vars)?;
self.send_html(to, "Ticket Resolved", html).await
}
// ── Documents Requested ─────────────────────────────────────────────────────
pub async fn send_documents_requested_email(&self, to: &str, name: &str, role_name: &str, document_request: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let profile_url = format!("{}/dashboard/profile", frontend_url);
let vars = HashMap::from([
("first_name", name),
("role_name", role_name),
("document_request", document_request),
("profile_url", &profile_url),
]);
let html = self.template_engine.render("documents-requested", vars)?;
self.send_html(to, "Additional Documents Required", html).await
}
// ── Application Status ──────────────────────────────────────────────────────
pub async fn send_application_status_email(&self, to: &str, name: &str, job_title: &str, status: &str) -> Result<()> {
let frontend_url = env::var("FRONTEND_URL").unwrap_or_else(|_| "https://nxtgauge.com".to_string());
let apps_url = format!("{}/dashboard/applications", frontend_url);
let (status_class, status_color, next_steps) = match status {
"SHORTLISTED" => ("status-pending", "#d97706", "The employer has shortlisted you. They may contact you soon for an interview."),
"INTERVIEW" => ("status-pending", "#d97706", "Congratulations! You've been selected for an interview. Check your email for scheduling details."),
"OFFERED" => ("status-approved", "#059669", "Great news! You've received a job offer. Please review the offer details in your dashboard."),
"HIRED" => ("status-approved", "#059669", "Congratulations on your new job! The employer will contact you with next steps."),
"REJECTED" => ("status-rejected", "#dc2626", "Unfortunately, you were not selected for this position. Don't give up - apply to other jobs!"),
_ => ("status-pending", "#6b7280", "Your application status has been updated."),
};
let now = chrono::Local::now().format("%B %d, %Y").to_string();
let vars = HashMap::from([
("first_name", name),
("job_title", job_title),
("company_name", "The employer"),
("status", status),
("status_class", status_class),
("status_color", status_color),
("next_steps", next_steps),
("note", ""),
("updated_at", &now),
("applications_url", &apps_url),
]);
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 {
fn default() -> Self {
Self::new()
}
}