package notifications import ( "fmt" "log" "net/smtp" "strings" ) type SMTPConfig struct { Host string Port int Username string Password string From string } func (c *SMTPConfig) IsConfigured() bool { return c.Host != "" && c.From != "" } func (c *SMTPConfig) addr() string { return fmt.Sprintf("%s:%d", c.Host, c.Port) } func SendEmailNotification(cfg SMTPConfig, toEmail, message string, meta AlertMetadata) (bool, error) { if !cfg.IsConfigured() { return false, fmt.Errorf("SMTP is not configured") } subject := fmt.Sprintf("Koin Ping Alert: %s", humanAlertType(meta.AlertType)) body := buildEmailBody(message, meta) msg := strings.Join([]string{ fmt.Sprintf("From: %s", cfg.From), fmt.Sprintf("To: %s", toEmail), fmt.Sprintf("Subject: %s", subject), "MIME-Version: 1.0", "Content-Type: text/html; charset=UTF-8", "", body, }, "\r\n") var auth smtp.Auth if cfg.Username != "" { auth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host) } err := smtp.SendMail(cfg.addr(), auth, cfg.From, []string{toEmail}, []byte(msg)) if err != nil { log.Printf("Failed to send email notification to %s: %v", toEmail, err) return false, err } return true, nil } func TestEmailNotification(cfg SMTPConfig, toEmail string) (bool, error) { meta := AlertMetadata{AlertType: "test"} return SendEmailNotification(cfg, toEmail, "This is a test notification from Koin Ping. Your email is configured correctly!", meta) } func buildEmailBody(message string, meta AlertMetadata) string { txSection := "" if meta.TxHash != "" { txSection = fmt.Sprintf( `Transaction`+ `View on Etherscan`, meta.TxHash, ) } return fmt.Sprintf(`

Koin Ping Alert

%s

%s
Address%s
Blockchain Address%s
Sent by Koin Ping
`, message, meta.AddressLabel, meta.Address, txSection) } func humanAlertType(alertType string) string { switch alertType { case "incoming_tx": return "Incoming Transaction" case "outgoing_tx": return "Outgoing Transaction" case "large_transfer": return "Large Transfer" case "balance_below": return "Balance Below Threshold" default: return "Alert" } }