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
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
}