package notification import ( "crypto/tls" "fmt" "net/smtp" "strconv" "strings" "time" ) // EmailService handles email notifications type EmailService struct{} // NewEmailService creates a new email notification service func NewEmailService() *EmailService { // log.Printf("✅ Email notification service initialized") return &EmailService{} } // SendNotification sends a notification via email func (es *EmailService) SendNotification(config *AlertConfiguration, message string) error { // log.Printf("📧 === SENDING EMAIL NOTIFICATION ===") // log.Printf("🔔 Email notification request received") // log.Printf("📊 Email Configuration:") // log.Printf(" - Email Address: %s", config.EmailAddress) // log.Printf(" - Sender Name: %s", config.EmailSenderName) // log.Printf(" - SMTP Server: %s", config.SMTPServer) // log.Printf(" - SMTP Port: %s", config.SMTPPort) // log.Printf(" - SMTP Password present: %t", config.SMTPPassword != "") // log.Printf(" - Message Length: %d chars", len(message)) // Validate email configuration if config.EmailAddress == "" || config.SMTPServer == "" || config.SMTPPort == "" { // log.Printf("❌ EMAIL CONFIGURATION ERROR: Missing required fields") // log.Printf(" - Email Address present: %t", config.EmailAddress != "") // log.Printf(" - SMTP Server present: %t", config.SMTPServer != "") // log.Printf(" - SMTP Port present: %t", config.SMTPPort != "") return fmt.Errorf("email configuration is incomplete") } if config.SMTPPassword == "" { // log.Printf("âš ī¸ WARNING: SMTP password not provided - authentication may fail") } port, err := strconv.Atoi(config.SMTPPort) if err != nil { // log.Printf("❌ SMTP PORT ERROR: Invalid port '%s': %v", config.SMTPPort, err) return fmt.Errorf("invalid SMTP port: %v", err) } // log.Printf("✅ Email configuration validation passed") // log.Printf("🔧 Parsed SMTP Port: %d", port) // Determine alert severity and get appropriate emoji/color severity, emoji := es.parseMessageSeverity(message) // log.Printf("📋 Message Analysis:") // log.Printf(" - Detected Severity: %s", severity) // log.Printf(" - Emoji: %s", emoji) // Create enhanced email content subject := es.createEmailSubject(config.EmailSenderName, severity) htmlBody := es.createHTMLEmailBody(message, severity, emoji) plainBody := es.createPlainEmailBody(message, severity, emoji) // log.Printf("📝 Email Content Created:") // log.Printf(" - Subject: %s", subject) // log.Printf(" - HTML Body Length: %d chars", len(htmlBody)) // log.Printf(" - Plain Body Length: %d chars", len(plainBody)) // Prepare email message with both HTML and plain text emailMessage := es.createMIMEMessage(config.EmailSenderName, config.EmailAddress, subject, htmlBody, plainBody) // log.Printf("📤 Preparing to send email...") // log.Printf(" - SMTP Server: %s:%d", config.SMTPServer, port) // log.Printf(" - From: %s", config.EmailSenderName) // log.Printf(" - To: %s", config.EmailAddress) // Send email with enhanced SMTP handling err = es.sendSMTPEmail(config.SMTPServer, port, config.EmailSenderName, config.EmailAddress, config.SMTPPassword, emailMessage) if err != nil { // log.Printf("❌ EMAIL SENDING FAILED: %v", err) // log.Printf("❌ Error Details:") // log.Printf(" - SMTP Server: %s:%d", config.SMTPServer, port) // log.Printf(" - Error Type: %T", err) // log.Printf(" - Error Message: %s", err.Error()) return fmt.Errorf("failed to send email: %v", err) } // log.Printf("✅ EMAIL SENT SUCCESSFULLY") // log.Printf("✅ Email Details:") // log.Printf(" - Recipient: %s", config.EmailAddress) // log.Printf(" - Subject: %s", subject) // log.Printf(" - Severity: %s", severity) // log.Printf(" - Timestamp: %s", time.Now().Format("2006-01-02 15:04:05")) // log.Printf("=== EMAIL NOTIFICATION COMPLETE ===") return nil } // parseMessageSeverity analyzes the message to determine severity level func (es *EmailService) parseMessageSeverity(message string) (string, string) { messageUpper := strings.ToUpper(message) // Check for critical indicators if strings.Contains(messageUpper, "🚨") || strings.Contains(messageUpper, "CRITICAL") || strings.Contains(messageUpper, "DOWN") || strings.Contains(messageUpper, "ERROR") || strings.Contains(messageUpper, "FAILED") || strings.Contains(messageUpper, "🔴") { return "CRITICAL", "🚨" } // Check for warning indicators if strings.Contains(messageUpper, "âš ī¸") || strings.Contains(messageUpper, "WARNING") || strings.Contains(messageUpper, "🟡") || strings.Contains(messageUpper, "INCIDENT") { return "WARNING", "âš ī¸" } // Check for success indicators if strings.Contains(messageUpper, "đŸŸĸ") || strings.Contains(messageUpper, "UP") || strings.Contains(messageUpper, "SUCCESS") || strings.Contains(messageUpper, "RESOLVED") || strings.Contains(messageUpper, "RESTORED") { return "SUCCESS", "✅" } // Check for maintenance indicators if strings.Contains(messageUpper, "🟠") || strings.Contains(messageUpper, "MAINTENANCE") || strings.Contains(messageUpper, "PAUSED") { return "MAINTENANCE", "🔧" } // Default to info return "INFO", "â„šī¸" } // createEmailSubject creates an appropriate email subject func (es *EmailService) createEmailSubject(senderName, severity string) string { prefix := "Service Alert" if senderName != "" { prefix = fmt.Sprintf("%s - Service Alert", senderName) } switch severity { case "CRITICAL": return fmt.Sprintf("🚨 [CRITICAL] %s", prefix) case "WARNING": return fmt.Sprintf("âš ī¸ [WARNING] %s", prefix) case "SUCCESS": return fmt.Sprintf("✅ [RESOLVED] %s", prefix) case "MAINTENANCE": return fmt.Sprintf("🔧 [MAINTENANCE] %s", prefix) default: return fmt.Sprintf("â„šī¸ [INFO] %s", prefix) } } // createHTMLEmailBody creates a rich HTML email body func (es *EmailService) createHTMLEmailBody(message, severity, emoji string) string { // Get color scheme based on severity var bgColor, borderColor, textColor string switch severity { case "CRITICAL": bgColor = "#fee2e2" borderColor = "#dc2626" textColor = "#991b1b" case "WARNING": bgColor = "#fef3c7" borderColor = "#d97706" textColor = "#92400e" case "SUCCESS": bgColor = "#dcfce7" borderColor = "#16a34a" textColor = "#15803d" case "MAINTENANCE": bgColor = "#fed7aa" borderColor = "#ea580c" textColor = "#c2410c" default: bgColor = "#dbeafe" borderColor = "#2563eb" textColor = "#1d4ed8" } // Format message content for HTML htmlMessage := strings.ReplaceAll(message, "\n", "
") // Enhance bullet points and formatting htmlMessage = strings.ReplaceAll(htmlMessage, " - ", "
  â€ĸ ") htmlMessage = strings.ReplaceAll(htmlMessage, "Service:", "Service:") htmlMessage = strings.ReplaceAll(htmlMessage, "Status:", "Status:") htmlMessage = strings.ReplaceAll(htmlMessage, "Host:", "Host:") htmlMessage = strings.ReplaceAll(htmlMessage, "Type:", "Type:") htmlMessage = strings.ReplaceAll(htmlMessage, "Response time:", "Response time:") htmlMessage = strings.ReplaceAll(htmlMessage, "Time:", "Time:") htmlMessage = strings.ReplaceAll(htmlMessage, "Domain:", "Domain:") htmlMessage = strings.ReplaceAll(htmlMessage, "Days Remaining:", "Days Remaining:") htmlMessage = strings.ReplaceAll(htmlMessage, "Expiration Date:", "Expiration Date:") return fmt.Sprintf(` Service Alert Notification

%s Service Alert Notification

%s

This is an automated notification from your monitoring system.
Generated at: %s

`, bgColor, textColor, borderColor, emoji, borderColor, htmlMessage, time.Now().Format("2006-01-02 15:04:05 MST")) } // createPlainEmailBody creates a plain text email body func (es *EmailService) createPlainEmailBody(message, severity, emoji string) string { separator := strings.Repeat("=", 50) return fmt.Sprintf(`%s %s SERVICE ALERT NOTIFICATION %s %s %s This is an automated notification from your monitoring system. Generated at: %s %s`, separator, emoji+" "+severity, separator, message, separator, time.Now().Format("2006-01-02 15:04:05 MST"), separator) } // createMIMEMessage creates a MIME message with both HTML and plain text func (es *EmailService) createMIMEMessage(fromName, toEmail, subject, htmlBody, plainBody string) string { boundary := fmt.Sprintf("boundary_%d", time.Now().Unix()) headers := fmt.Sprintf(`From: %s To: %s Subject: %s MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="%s" `, fromName, toEmail, subject, boundary) body := fmt.Sprintf(`--%s Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit %s --%s Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit %s --%s-- `, boundary, plainBody, boundary, htmlBody, boundary) return headers + body } // sendSMTPEmail sends the email using SMTP with proper authentication func (es *EmailService) sendSMTPEmail(smtpServer string, port int, fromEmail, toEmail, password, message string) error { addr := fmt.Sprintf("%s:%d", smtpServer, port) // log.Printf("🔌 Connecting to SMTP server: %s", addr) // Extract hostname from SMTP server for proper HELO hostname := smtpServer if strings.Contains(hostname, ".") { // Use the SMTP server hostname for HELO hostname = smtpServer } // log.Printf("🔧 Using hostname for HELO: %s", hostname) // For port 587 (STARTTLS) - most common for authenticated SMTP if port == 587 { // log.Printf("🔐 Attempting STARTTLS connection with authentication...") return es.sendWithSTARTTLSAuth(addr, hostname, fromEmail, toEmail, password, message) } // For port 465 (SSL/TLS) if port == 465 { // log.Printf("🔒 Attempting SSL connection with authentication...") return es.sendWithSSLAuth(addr, hostname, fromEmail, toEmail, password, message) } // For port 25 (Plain SMTP with optional STARTTLS) if port == 25 { // log.Printf("📧 Attempting plain SMTP with optional STARTTLS...") return es.sendWithSTARTTLSAuth(addr, hostname, fromEmail, toEmail, password, message) } // Fallback to STARTTLS for any other port // log.Printf("📧 Using STARTTLS with auth fallback for port %d...", port) return es.sendWithSTARTTLSAuth(addr, hostname, fromEmail, toEmail, password, message) } // sendWithSTARTTLSAuth sends email with STARTTLS and authentication func (es *EmailService) sendWithSTARTTLSAuth(addr, hostname, fromEmail, toEmail, password, message string) error { // log.Printf("🔐 Establishing STARTTLS connection to %s", addr) // Connect to server client, err := smtp.Dial(addr) if err != nil { // log.Printf("❌ Failed to connect to SMTP server: %v", err) return fmt.Errorf("failed to connect to SMTP server: %v", err) } defer client.Close() // Send EHLO with proper hostname // log.Printf("👋 Sending EHLO with hostname: %s", hostname) if err = client.Hello(hostname); err != nil { // log.Printf("❌ EHLO failed: %v", err) return fmt.Errorf("EHLO failed: %v", err) } // Check if STARTTLS is supported and use it if ok, _ := client.Extension("STARTTLS"); ok { // log.Printf("🔐 STARTTLS supported, initiating TLS...") tlsConfig := &tls.Config{ ServerName: strings.Split(addr, ":")[0], InsecureSkipVerify: false, } if err = client.StartTLS(tlsConfig); err != nil { // log.Printf("❌ STARTTLS failed: %v", err) return fmt.Errorf("STARTTLS failed: %v", err) } // log.Printf("✅ TLS connection established") } else { // log.Printf("âš ī¸ STARTTLS not supported by server, continuing with plain connection") } // Check for AUTH support and authenticate if available if ok, mechanisms := client.Extension("AUTH"); ok { // Suppress unused variable warning _ = mechanisms // log.Printf("🔑 AUTH extension supported with mechanisms: %s", mechanisms) // Use the provided credentials for authentication username := fromEmail // log.Printf("🔐 Attempting authentication for user: %s", username) // log.Printf("🔑 Password provided: %t", password != "") if password != "" { // Create auth mechanism - try PLAIN first as it's most common auth := smtp.PlainAuth("", username, password, strings.Split(addr, ":")[0]) if err := client.Auth(auth); err != nil { // log.Printf("❌ Authentication failed: %v", err) return fmt.Errorf("SMTP authentication failed: %v", err) } else { // log.Printf("✅ Authentication successful") } } else { // log.Printf("âš ī¸ No password provided, skipping authentication") return fmt.Errorf("SMTP password is required for authentication") } } else { // log.Printf("â„šī¸ AUTH extension not available, proceeding without authentication") } // Set sender // log.Printf("📤 Setting sender: %s", fromEmail) if err = client.Mail(fromEmail); err != nil { // log.Printf("❌ Failed to set sender: %v", err) return fmt.Errorf("failed to set sender: %v", err) } // Set recipient // log.Printf("đŸ“Ĩ Setting recipient: %s", toEmail) if err = client.Rcpt(toEmail); err != nil { // log.Printf("❌ Failed to set recipient: %v", err) return fmt.Errorf("failed to set recipient: %v", err) } // Send message // log.Printf("📝 Sending message data...") w, err := client.Data() if err != nil { // log.Printf("❌ Failed to initiate data transfer: %v", err) return fmt.Errorf("failed to initiate data transfer: %v", err) } _, err = w.Write([]byte(message)) if err != nil { // log.Printf("❌ Failed to write message data: %v", err) return fmt.Errorf("failed to write message data: %v", err) } err = w.Close() if err != nil { // log.Printf("❌ Failed to close data writer: %v", err) return fmt.Errorf("failed to close data writer: %v", err) } // Quit gracefully err = client.Quit() if err != nil { // log.Printf("âš ī¸ Warning during QUIT: %v", err) // Don't return error for QUIT issues as email might have been sent } // log.Printf("✅ Email sent successfully via STARTTLS") return nil } // sendWithSSLAuth sends email with SSL/TLS and authentication (for port 465) func (es *EmailService) sendWithSSLAuth(addr, hostname, fromEmail, toEmail, password, message string) error { // log.Printf("🔒 Establishing SSL/TLS connection to %s", addr) // Create TLS configuration tlsConfig := &tls.Config{ InsecureSkipVerify: false, ServerName: strings.Split(addr, ":")[0], } // Connect with TLS conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { // log.Printf("❌ Failed to establish TLS connection: %v", err) return fmt.Errorf("failed to establish TLS connection: %v", err) } defer conn.Close() // Create SMTP client client, err := smtp.NewClient(conn, strings.Split(addr, ":")[0]) if err != nil { // log.Printf("❌ Failed to create SMTP client: %v", err) return fmt.Errorf("failed to create SMTP client: %v", err) } defer client.Quit() // Send EHLO with proper hostname // log.Printf("👋 Sending EHLO with hostname: %s", hostname) if err = client.Hello(hostname); err != nil { // log.Printf("❌ EHLO failed: %v", err) return fmt.Errorf("EHLO failed: %v", err) } // Authenticate if AUTH is supported and password is provided if ok, mechanisms := client.Extension("AUTH"); ok { // Suppress unused variable warning _ = mechanisms // log.Printf("🔑 AUTH extension supported with mechanisms: %s", mechanisms) username := fromEmail // log.Printf("🔐 Attempting authentication for user: %s", username) // log.Printf("🔑 Password provided: %t", password != "") if password != "" { auth := smtp.PlainAuth("", username, password, strings.Split(addr, ":")[0]) if err := client.Auth(auth); err != nil { // log.Printf("❌ Authentication failed: %v", err) return fmt.Errorf("SMTP authentication failed: %v", err) } else { // log.Printf("✅ Authentication successful") } } else { // log.Printf("âš ī¸ No password provided, skipping authentication") return fmt.Errorf("SMTP password is required for authentication") } } // Set sender and recipient // log.Printf("📤 Setting sender: %s", fromEmail) if err := client.Mail(fromEmail); err != nil { // log.Printf("❌ Failed to set sender: %v", err) return fmt.Errorf("failed to set sender: %v", err) } // log.Printf("đŸ“Ĩ Setting recipient: %s", toEmail) if err := client.Rcpt(toEmail); err != nil { // log.Printf("❌ Failed to set recipient: %v", err) return fmt.Errorf("failed to set recipient: %v", err) } // Send message // log.Printf("📝 Sending message data...") w, err := client.Data() if err != nil { // log.Printf("❌ Failed to initiate data transfer: %v", err) return fmt.Errorf("failed to initiate data transfer: %v", err) } _, err = w.Write([]byte(message)) if err != nil { // log.Printf("❌ Failed to write message data: %v", err) return fmt.Errorf("failed to write message data: %v", err) } err = w.Close() if err != nil { // log.Printf("❌ Failed to close data writer: %v", err) return fmt.Errorf("failed to close data writer: %v", err) } // log.Printf("✅ Email sent successfully via SSL/TLS") return nil }