别着急,坐和放宽
使用社交账号登录
在企业应用开发中,邮件告警是一个非常重要的功能模块。当系统出现异常、服务宕机或需要及时通知相关人员时,邮件告警能够确保信息及时传达。本教程将手把手教你如何使用Go语言实现一个功能完善的邮件告警系统。
通过本教程,你将学会:
net/smtp包发送邮件


SMTP服务器: smtp.gmail.com
端口: 587
加密方式: STARTTLS
用户名: 你的Gmail邮箱地址
密码: 刚才生成的应用专用密码

如果你的企业邮箱设置了安全策略,可能需要:

SMTP服务器: smtp.exmail.qq.com
端口: 465
加密方式: SSL/TLS
用户名: 你的企业邮箱地址
密码: 邮箱密码或授权码
如果你使用的是QQ个人邮箱:
SMTP服务器: smtp.qq.com
端口: 465或587
加密方式: SSL/TLS
用户名: 你的QQ邮箱地址
密码: 生成的授权码(不是QQ密码)
首先,我们定义配置文件结构:
config/config.go
config.yaml
util/email_alert.go
main.go
原因:
解决方法:
原因:
解决方法:
错误信息: x509: certificate signed by unknown authority
解决方法:
原因:
解决方法:
原因:
解决方法:
Gmail限制:
QQ邮箱限制:
解决方法:
对于高频发送场景,可以实现SMTP连接池:
InsecureSkipVerify: true通过本教程,我们学习了:
这个邮件告警系统可以广泛应用于:
希望这个教程对你有帮助!如果在实践过程中遇到问题,欢迎查阅常见问题章节或参考相关文档。
go get go.uber.org/zap
go get github.com/spf13/viper
email-alert-system/
├── main.go # 主程序入口
├── config/
│ └── config.go # 配置管理
├── util/
│ └── email_alert.go # 邮件告警核心代码
└── config.yaml # 配置文件
package config
import (
"log"
"github.com/spf13/viper"
)
type Config struct {
Email Email `yaml:"email"`
}
type Email struct {
AlertEmail string `yaml:"alertEmail"` // Sender email address
Password string `yaml:"password"` // Email password or app-specific password
Provider string `yaml:"provider"` // Email provider: gmail, tencent, qq
Enable bool `yaml:"enable"` // Enable/disable email alerts
}
// LoadConfig loads configuration from YAML file
func LoadConfig(configPath string) (*Config, error) {
viper.SetConfigFile(configPath)
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, err
}
return &config, nil
}
email:
alertEmail: "[email protected]" # Your email address
password: "your-app-password" # Your app-specific password
provider: "gmail" # Options: gmail, tencent, qq
enable: true # Enable email alerts
package util
import (
"crypto/tls"
"encoding/base64"
"fmt"
"net/smtp"
"strings"
"time"
"go.uber.org/zap"
)
// AlertType defines the severity level of alerts
type AlertType string
const (
INFO AlertType = "INFO"
WARNING AlertType = "WARNING"
ERROR AlertType = "ERROR"
CRITICAL AlertType = "CRITICAL"
)
// SMTPConfig holds SMTP server configuration
type SMTPConfig struct {
Server string
Port int
UseTLS bool // Use TLS direct connection (port 465)
Username string
Password string
}
// EmailAlert is the main email alert service
type EmailAlert struct {
config SMTPConfig
enable bool
logger *zap.Logger
}
// NewEmailAlert creates a new email alert service
func NewEmailAlert(alertEmail, password, provider string, enable bool) *EmailAlert {
var config SMTPConfig
switch strings.ToLower(provider) {
case "gmail", "google":
config = SMTPConfig{
Server: "smtp.gmail.com",
Port: 587,
UseTLS: false, // Gmail uses STARTTLS on port 587
Username: alertEmail,
Password: password,
}
case "qq":
config = SMTPConfig{
Server: "smtp.qq.com",
Port: 465,
UseTLS: true, // QQ mail uses direct TLS
Username: alertEmail,
Password: password,
}
default:
// Default to Tencent Corporate Email
config = SMTPConfig{
Server: "smtp.exmail.qq.com",
Port: 465,
UseTLS: true,
Username: alertEmail,
Password: password,
}
}
logger, _ := zap.NewProduction()
return &EmailAlert{
config: config,
enable: enable,
logger: logger.Named("EmailAlert"),
}
}
// SendAlert sends an alert email with specified type
func (e *EmailAlert) SendAlert(recipients []string, subject, body string, alertType AlertType) error {
if !e.enable {
e.logger.Info("Email alerts are disabled, skipping...")
return nil
}
message := e.buildMessage(recipients, subject, body, alertType)
addr := fmt.Sprintf("%s:%d", e.config.Server, e.config.Port)
var err error
var client *smtp.Client
// Establish connection based on TLS settings
if e.config.UseTLS {
// Direct TLS connection (port 465)
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: e.config.Server,
}
conn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
return fmt.Errorf("TLS connection failed: %v", err)
}
client, err = smtp.NewClient(conn, e.config.Server)
if err != nil {
return fmt.Errorf("failed to create SMTP client: %v", err)
}
} else {
// Plain connection with STARTTLS (port 587)
client, err = smtp.Dial(addr)
if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %v", err)
}
// Upgrade to TLS using STARTTLS for Gmail
if e.config.Server == "smtp.gmail.com" {
tlsConfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: e.config.Server,
}
if err = client.StartTLS(tlsConfig); err != nil {
client.Close()
return fmt.Errorf("STARTTLS failed: %v", err)
}
}
}
defer client.Close()
// Authenticate
auth := smtp.PlainAuth("", e.config.Username, e.config.Password, e.config.Server)
if err = client.Auth(auth); err != nil {
return fmt.Errorf("SMTP authentication failed: %v", err)
}
// Set sender
if err = client.Mail(e.config.Username); err != nil {
return fmt.Errorf("failed to set sender: %v", err)
}
// Set recipients
for _, recipient := range recipients {
if err = client.Rcpt(recipient); err != nil {
return fmt.Errorf("failed to set recipient %s: %v", recipient, err)
}
}
// Send email content
writer, err := client.Data()
if err != nil {
return fmt.Errorf("failed to send email content: %v", err)
}
_, err = writer.Write([]byte(message))
if err != nil {
return fmt.Errorf("failed to write message content: %v", err)
}
err = writer.Close()
if err != nil {
return fmt.Errorf("failed to complete email delivery: %v", err)
}
e.logger.Info("Alert email sent successfully",
zap.String("type", string(alertType)),
zap.Any("recipients", recipients))
return nil
}
// encodeRFC2047 encodes a string using RFC 2047 for email headers
func (e *EmailAlert) encodeRFC2047(s string) string {
// RFC 2047 format: =?UTF-8?B?<base64-encoded-text>?=
return fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(s)))
}
// buildMessage constructs the email message with HTML formatting
func (e *EmailAlert) buildMessage(recipients []string, subject, body string, alertType AlertType) string {
timestamp := time.Now().Format("2006-01-02 15:04:05")
// Add emoji prefix based on alert type
var prefix string
switch alertType {
case INFO:
prefix = "ℹ️ [INFO]"
case WARNING:
prefix = "⚠️ [WARNING]"
case ERROR:
prefix = "❌ [ERROR]"
case CRITICAL:
prefix = "🚨 [CRITICAL]"
default:
prefix = "📧 [NOTIFY]"
}
fullSubject := fmt.Sprintf("%s %s", prefix, subject)
// Build HTML email body
htmlBody := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
background-color: %s;
color: white;
padding: 20px;
text-align: center;
}
.header h2 {
margin: 0;
font-size: 24px;
}
.content {
padding: 30px;
border-left: 4px solid %s;
background-color: #fafafa;
margin: 20px;
border-radius: 4px;
}
.info-row {
margin: 10px 0;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.info-row:last-child {
border-bottom: none;
}
.label {
font-weight: bold;
color: #555;
display: inline-block;
min-width: 100px;
}
.value {
color: #333;
}
.timestamp {
font-weight: bold;
color: #333;
}
.message-content {
margin-top: 20px;
padding: 15px;
background-color: white;
border-radius: 4px;
line-height: 1.6;
}
.footer {
margin-top: 20px;
padding: 20px;
font-size: 12px;
color: #666;
text-align: center;
background-color: #f9f9f9;
}
.footer p {
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>%s</h2>
</div>
<div class="content">
<div class="info-row">
<span class="label">Alert Time:</span>
<span class="value timestamp">%s</span>
</div>
<div class="info-row">
<span class="label">Alert Level:</span>
<span class="value">%s</span>
</div>
<div class="message-content">
%s
</div>
</div>
<div class="footer">
<p>⚙️ This is an automated system email, please do not reply directly</p>
<p>If you have any questions, please contact your system administrator</p>
</div>
</div>
</body>
</html>`,
e.getAlertColor(alertType),
e.getAlertColor(alertType),
fullSubject,
timestamp,
string(alertType),
strings.ReplaceAll(body, "\n", "<br>"))
// Build complete email message with required RFC5322 headers
// Generate RFC5322 compliant date
rfcDate := time.Now().Format(time.RFC1123Z)
// Encode subject according to RFC2047
encodedSubject := e.encodeRFC2047(fullSubject)
message := fmt.Sprintf("From: %s\r\n"+
"To: %s\r\n"+
"Subject: %s\r\n"+
"Date: %s\r\n"+
"MIME-Version: 1.0\r\n"+
"Content-Type: text/html; charset=UTF-8\r\n"+
"\r\n"+
"%s",
e.config.Username,
strings.Join(recipients, ", "),
encodedSubject,
rfcDate,
htmlBody)
return message
}
// getAlertColor returns the color code for each alert type
func (e *EmailAlert) getAlertColor(alertType AlertType) string {
colors := map[AlertType]string{
INFO: "#17a2b8", // Light blue
WARNING: "#ffc107", // Yellow
ERROR: "#dc3545", // Red
CRITICAL: "#6f42c1", // Purple
}
if color, exists := colors[alertType]; exists {
return color
}
return "#6c757d" // Gray
}
// SendSystemAlert sends a system error alert
func (e *EmailAlert) SendSystemAlert(recipients []string, systemName, errorMsg string) error {
if !e.enable {
return nil
}
subject := fmt.Sprintf("System Alert - %s", systemName)
body := fmt.Sprintf(`
<strong>System:</strong> %s<br>
<strong>Time:</strong> %s<br>
<strong>Error Message:</strong><br>
<pre style="background-color: #f4f4f4; padding: 10px; border-radius: 4px;">%s</pre>
`,
systemName,
time.Now().Format("2006-01-02 15:04:05"),
errorMsg)
return e.SendAlert(recipients, subject, body, ERROR)
}
// SendServiceDownAlert sends a service down alert
func (e *EmailAlert) SendServiceDownAlert(recipients []string, serviceName string) error {
if !e.enable {
return nil
}
subject := fmt.Sprintf("Service Down Alert - %s", serviceName)
body := fmt.Sprintf(`
<strong>Service Name:</strong> %s<br>
<strong>Status:</strong> <span style="color: red;">Service Unavailable</span><br>
<strong>Time:</strong> %s<br>
<br>
<p style="color: red; font-weight: bold;">⚠️ Please check the service status immediately!</p>
`,
serviceName,
time.Now().Format("2006-01-02 15:04:05"))
return e.SendAlert(recipients, subject, body, CRITICAL)
}
package main
import (
"fmt"
"log"
"time"
"your-project/config"
"your-project/util"
)
func main() {
// Load configuration
cfg, err := config.LoadConfig("config.yaml")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Create email alert service
emailAlert := util.NewEmailAlert(
cfg.Email.AlertEmail,
cfg.Email.Password,
cfg.Email.Provider,
cfg.Email.Enable,
)
// Example 1: Send INFO level alert
fmt.Println("Sending INFO alert...")
err = emailAlert.SendAlert(
[]string{"[email protected]"},
"System Startup Notification",
"The application has started successfully.<br>All services are running normally.",
util.INFO,
)
if err != nil {
log.Printf("Failed to send INFO alert: %v", err)
}
time.Sleep(2 * time.Second)
// Example 2: Send WARNING level alert
fmt.Println("Sending WARNING alert...")
err = emailAlert.SendAlert(
[]string{"[email protected]"},
"High Memory Usage Warning",
"Current memory usage: 85%<br>Please pay attention to system resource consumption.",
util.WARNING,
)
if err != nil {
log.Printf("Failed to send WARNING alert: %v", err)
}
time.Sleep(2 * time.Second)
// Example 3: Send ERROR level alert
fmt.Println("Sending ERROR alert...")
err = emailAlert.SendSystemAlert(
[]string{"[email protected]"},
"Payment Service",
"Database connection failed: timeout after 30 seconds",
)
if err != nil {
log.Printf("Failed to send ERROR alert: %v", err)
}
time.Sleep(2 * time.Second)
// Example 4: Send CRITICAL level alert
fmt.Println("Sending CRITICAL alert...")
err = emailAlert.SendServiceDownAlert(
[]string{"[email protected]", "[email protected]"},
"Core API Service",
)
if err != nil {
log.Printf("Failed to send CRITICAL alert: %v", err)
}
fmt.Println("All test emails sent!")
}
// 1. Create email alert service
emailAlert := util.NewEmailAlert(
"[email protected]",
"your-app-password",
"gmail",
true,
)
// 2. Send a simple alert
err := emailAlert.SendAlert(
[]string{"[email protected]"},
"Test Subject",
"Test email body content",
util.INFO,
)
package main
import (
"log"
"net/http"
"your-project/util"
)
var emailAlert *util.EmailAlert
func init() {
// Initialize email alert service
emailAlert = util.NewEmailAlert(
"[email protected]",
"your-app-password",
"gmail",
true,
)
}
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
// Simulate health check
if !checkDatabaseConnection() {
// Send alert when database is down
emailAlert.SendSystemAlert(
[]string{"[email protected]"},
"Web Application",
"Database connection check failed",
)
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
return
}
w.Write([]byte("OK"))
}
func checkDatabaseConnection() bool {
// Your database check logic
return true
}
func main() {
http.HandleFunc("/health", healthCheckHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
package main
import (
"time"
"your-project/util"
)
func monitorSystemResources(emailAlert *util.EmailAlert) {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
cpuUsage := getCPUUsage()
memUsage := getMemoryUsage()
// CPU warning
if cpuUsage > 80 {
emailAlert.SendAlert(
[]string{"[email protected]"},
"High CPU Usage Alert",
fmt.Sprintf("Current CPU usage: %.2f%%", cpuUsage),
util.WARNING,
)
}
// Memory critical alert
if memUsage > 90 {
emailAlert.SendAlert(
[]string{"[email protected]", "[email protected]"},
"Critical Memory Usage",
fmt.Sprintf("Current memory usage: %.2f%%", memUsage),
util.CRITICAL,
)
}
}
}
func getCPUUsage() float64 {
// Your CPU monitoring logic
return 75.5
}
func getMemoryUsage() float64 {
// Your memory monitoring logic
return 82.3
}
// 临时解决方案(不推荐用于生产环境)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: e.config.Server,
}
// 推荐方案:安装正确的证书
// 在生产环境中应该使用系统的证书池
package util
import (
"sync"
)
type EmailQueue struct {
emailAlert *EmailAlert
queue chan EmailTask
wg sync.WaitGroup
}
type EmailTask struct {
Recipients []string
Subject string
Body string
AlertType AlertType
}
func NewEmailQueue(emailAlert *EmailAlert, workers int) *EmailQueue {
eq := &EmailQueue{
emailAlert: emailAlert,
queue: make(chan EmailTask, 100),
}
// Start workers
for i := 0; i < workers; i++ {
eq.wg.Add(1)
go eq.worker()
}
return eq
}
func (eq *EmailQueue) worker() {
defer eq.wg.Done()
for task := range eq.queue {
eq.emailAlert.SendAlert(
task.Recipients,
task.Subject,
task.Body,
task.AlertType,
)
}
}
func (eq *EmailQueue) AddTask(task EmailTask) {
eq.queue <- task
}
func (eq *EmailQueue) Close() {
close(eq.queue)
eq.wg.Wait()
}
package util
import (
"bytes"
"html/template"
)
type EmailTemplate struct {
templates map[string]*template.Template
}
func NewEmailTemplate() *EmailTemplate {
return &EmailTemplate{
templates: make(map[string]*template.Template),
}
}
func (et *EmailTemplate) RegisterTemplate(name, tmpl string) error {
t, err := template.New(name).Parse(tmpl)
if err != nil {
return err
}
et.templates[name] = t
return nil
}
func (et *EmailTemplate) Render(name string, data interface{}) (string, error) {
tmpl, exists := et.templates[name]
if !exists {
return "", fmt.Errorf("template %s not found", name)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return "", err
}
return buf.String(), nil
}
func (e *EmailAlert) SendAlertWithRetry(recipients []string, subject, body string, alertType AlertType, maxRetries int) error {
var err error
for i := 0; i < maxRetries; i++ {
err = e.SendAlert(recipients, subject, body, alertType)
if err == nil {
return nil
}
e.logger.Warn("Email send failed, retrying...",
zap.Int("attempt", i+1),
zap.Error(err))
// Exponential backoff
time.Sleep(time.Duration(1<<uint(i)) * time.Second)
}
return fmt.Errorf("failed after %d retries: %v", maxRetries, err)
}
type SMTPPool struct {
pool chan *smtp.Client
config SMTPConfig
}
// Implementation details...
func (e *EmailAlert) SendAlertAsync(recipients []string, subject, body string, alertType AlertType) {
go func() {
if err := e.SendAlert(recipients, subject, body, alertType); err != nil {
e.logger.Error("Async email send failed", zap.Error(err))
}
}()
}
func (e *EmailAlert) SendBatchAlerts(tasks []EmailTask) error {
for _, task := range tasks {
if err := e.SendAlert(task.Recipients, task.Subject, task.Body, task.AlertType); err != nil {
e.logger.Error("Batch email send failed", zap.Error(err))
continue
}
// Rate limiting
time.Sleep(100 * time.Millisecond)
}
return nil
}
import "os"
emailAlert := util.NewEmailAlert(
os.Getenv("EMAIL_ADDRESS"),
os.Getenv("EMAIL_PASSWORD"),
os.Getenv("EMAIL_PROVIDER"),
true,
)
func validateEmail(email string) bool {
// Simple email validation
return strings.Contains(email, "@") && strings.Contains(email, ".")
}
func (e *EmailAlert) SendAlertSafe(recipients []string, subject, body string, alertType AlertType) error {
// Validate recipients
for _, recipient := range recipients {
if !validateEmail(recipient) {
return fmt.Errorf("invalid email address: %s", recipient)
}
}
return e.SendAlert(recipients, subject, body, alertType)
}