diff --git a/cmd/mailtest/main.go b/cmd/mailtest/main.go new file mode 100644 index 0000000..1b4c08a --- /dev/null +++ b/cmd/mailtest/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "crypto/tls" + "fmt" + "log" + "net" + "net/smtp" + "os" + "strings" + "time" +) + +func main() { + smtpHost := getenv("SMTP_HOST", "smtp.163.com") + smtpPort := getenv("SMTP_PORT", "465") + smtpUser := strings.TrimSpace(os.Getenv("SMTP_USER")) + smtpPass := strings.TrimSpace(os.Getenv("SMTP_PASS")) + mailTo := strings.TrimSpace(os.Getenv("MAIL_TO")) + subject := getenv("MAIL_SUBJECT", "SuperTodo SMTP test") + body := getenv("MAIL_BODY", "This is a SMTP connectivity test email from SuperTodo.") + + if smtpUser == "" || smtpPass == "" || mailTo == "" { + log.Fatal("missing required env: SMTP_USER, SMTP_PASS, MAIL_TO") + } + + if err := sendMailTLS(smtpHost, smtpPort, smtpUser, smtpPass, mailTo, subject, body); err != nil { + log.Fatalf("send mail failed: %v", err) + } + + log.Printf("mail sent successfully: from=%s to=%s via %s:%s", smtpUser, mailTo, smtpHost, smtpPort) +} + +func sendMailTLS(host, port, user, pass, to, subject, body string) error { + addr := net.JoinHostPort(host, port) + dialer := &net.Dialer{Timeout: 10 * time.Second} + conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{ + ServerName: host, + MinVersion: tls.VersionTLS12, + }) + if err != nil { + return fmt.Errorf("tls dial failed: %w", err) + } + defer conn.Close() + + client, err := smtp.NewClient(conn, host) + if err != nil { + return fmt.Errorf("smtp client failed: %w", err) + } + defer client.Close() + + auth := smtp.PlainAuth("", user, pass, host) + if err := client.Auth(auth); err != nil { + return fmt.Errorf("smtp auth failed: %w", err) + } + if err := client.Mail(user); err != nil { + return fmt.Errorf("mail from failed: %w", err) + } + if err := client.Rcpt(to); err != nil { + return fmt.Errorf("rcpt to failed: %w", err) + } + + w, err := client.Data() + if err != nil { + return fmt.Errorf("data failed: %w", err) + } + + msg := buildMessage(user, to, subject, body) + if _, err := w.Write([]byte(msg)); err != nil { + return fmt.Errorf("write message failed: %w", err) + } + if err := w.Close(); err != nil { + return fmt.Errorf("close writer failed: %w", err) + } + if err := client.Quit(); err != nil { + return fmt.Errorf("smtp quit failed: %w", err) + } + return nil +} + +func buildMessage(from, to, subject, body string) string { + var b strings.Builder + b.WriteString("From: " + from + "\r\n") + b.WriteString("To: " + to + "\r\n") + b.WriteString("Subject: " + subject + "\r\n") + b.WriteString("MIME-Version: 1.0\r\n") + b.WriteString("Content-Type: text/plain; charset=UTF-8\r\n") + b.WriteString("\r\n") + b.WriteString(body) + if !strings.HasSuffix(body, "\n") { + b.WriteString("\r\n") + } + return b.String() +} + +func getenv(key, fallback string) string { + v := strings.TrimSpace(os.Getenv(key)) + if v == "" { + return fallback + } + return v +} + diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..17dd61f --- /dev/null +++ b/config.toml @@ -0,0 +1,38 @@ +[app] +name = "super-todo" +env = "dev" +http_addr = ":8080" + +[database] +url = "postgres://todo:todo@localhost:5432/todo?sslmode=disable" + +[auth] +secret = "dev-secret-change-me" +access_token_ttl_minutes = 15 +refresh_token_ttl_hours = 168 + +[redis] +addr = "localhost:6379" +password = "" +db = 0 + +[kafka] +brokers = ["localhost:29092"] +topic = "todo.tasks" + +[mail.smtp] +host = "smtp.qq.com" +port = 465 +username = "2914037183@qq.com" +password = "daxnelwgfaraddbi" +from = "2914037183@qq.com" +use_tls = true + +[notification] +enabled = true +timezone = "Asia/Shanghai" + +[notification.schedule] +daily_summary_cron = "0 30 9 * * *" +overdue_reminder_cron = "0 0/30 * * * *" +