From 89bc723427214ca357a4f9c25b162ad42a1ed91a Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 5 Sep 2024 17:54:39 +0200 Subject: [PATCH] feat(mail): #132 unify env variables and send mails with smtp --- Readme.md | 7 ++++++ handler.go | 5 ++++ main.go | 31 +++++++++++++++---------- middleware/cors.go | 12 +++------- utils/db.go | 2 +- utils/env.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++ utils/mail.go | 19 ++++++++++++++++ 7 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 utils/env.go create mode 100644 utils/mail.go diff --git a/Readme.md b/Readme.md index 2d9d0b8..88f83ba 100644 --- a/Readme.md +++ b/Readme.md @@ -35,3 +35,10 @@ As of 2024 there are 4 options: Even though I would really implement authentication myself, I think OAuth2 with external providers is the best bet. Especially because my reasoning is privacy, which most people just don't care about enough. Using this approach, adding in a keycloak is possible without breaking changes at a later point, as long as I keep the Google Sign In. +### Email + +For Email verification, etc. a mail server is needed, that can send a whole lot of mails. Aditionally, a mail account is needed for incoming mails. I thought about self hosting, but unfortunatly this is a hastle to maintain. Not only you have to setup a mail server, which is not as easy as it sounds, you also have to "register" your mail server for diffrent providers. Otherwise you are not able to send and receive emails. Thus, the first external service is needed. + +In order to not vendor lock in, I decided to use an SMTP relay in favor of a vendor specific API. You are free to choose a transactional mail provider. I chose brevo.com. They have a generous free tier of 300 mails per day. You can either upgrade to a monthly plan 10$ for 20k mails or buy credits for 30$ for 5k mails. Most provider provide 100 mails / day for free. + + diff --git a/handler.go b/handler.go index 1fa24bc..6610234 100644 --- a/handler.go +++ b/handler.go @@ -3,6 +3,7 @@ package main import ( "me-fit/middleware" "me-fit/service" + "me-fit/utils" "database/sql" "net/http" @@ -13,6 +14,10 @@ func getHandler(db *sql.DB) http.Handler { router.HandleFunc("/", service.HandleIndexAnd404(db)) + router.HandleFunc("/mail", func(w http.ResponseWriter, r *http.Request) { + utils.SendWelcomeMail("timwundenberg@outlook.de") + }) + // Serve static files (CSS, JS and images) router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) diff --git a/main.go b/main.go index b567da9..54e8d25 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ func main() { if err != nil { log.Fatal("Error loading .env file") } + utils.MustInitEnv() db, err := sql.Open("sqlite3", "./data.db") if err != nil { @@ -27,19 +28,9 @@ func main() { } defer db.Close() - utils.RunMigrations(db) + utils.MustRunMigrations(db) - var prometheusServer = http.Server{ - Addr: ":8081", - Handler: promhttp.Handler(), - } - go func() { - slog.Info("Starting prometheus server on " + prometheusServer.Addr) - err := prometheusServer.ListenAndServe() - if err != nil { - panic(err) - } - }() + startPrometheus() var server = http.Server{ Addr: ":8080", @@ -52,3 +43,19 @@ func main() { panic(err) } } + +func startPrometheus() { + + var prometheusServer = http.Server{ + Addr: ":8081", + Handler: promhttp.Handler(), + } + + go func() { + slog.Info("Starting prometheus server on " + prometheusServer.Addr) + err := prometheusServer.ListenAndServe() + if err != nil { + log.Fatal("Could not start prometheus server: ", err) + } + }() +} diff --git a/middleware/cors.go b/middleware/cors.go index 05ac4ac..decd71f 100644 --- a/middleware/cors.go +++ b/middleware/cors.go @@ -1,21 +1,15 @@ package middleware import ( - "log" - "log/slog" + "me-fit/utils" + "net/http" - "os" ) func EnableCors(next http.Handler) http.Handler { - var base_url = os.Getenv("BASE_URL") - if base_url == "" { - log.Fatal("BASE_URL is not set") - } - slog.Info("BASE_URL is " + base_url) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", base_url) + w.Header().Set("Access-Control-Allow-Origin", utils.BaseUrl) w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE") if r.Method == "OPTIONS" { diff --git a/utils/db.go b/utils/db.go index 7e2aa2a..90f782e 100644 --- a/utils/db.go +++ b/utils/db.go @@ -9,7 +9,7 @@ import ( _ "github.com/golang-migrate/migrate/v4/source/file" ) -func RunMigrations(db *sql.DB) { +func MustRunMigrations(db *sql.DB) { driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) if err != nil { log.Fatal(err) diff --git a/utils/env.go b/utils/env.go new file mode 100644 index 0000000..f6de698 --- /dev/null +++ b/utils/env.go @@ -0,0 +1,57 @@ +package utils + +import ( + "log" + "log/slog" + "os" +) + +var ( + SmtpHost string + SmtpPort string + SmtpUser string + SmtpPass string + SmtpFromMail string + SmtpFromName string + BaseUrl string +) + +func MustInitEnv() { + SmtpHost = os.Getenv("SMTP_HOST") + SmtpPort = os.Getenv("SMTP_PORT") + SmtpUser = os.Getenv("SMTP_USER") + SmtpPass = os.Getenv("SMTP_PASS") + SmtpFromMail = os.Getenv("SMTP_FROM_MAIL") + SmtpFromName = os.Getenv("SMTP_FROM_NAME") + BaseUrl = os.Getenv("BASE_URL") + + if SmtpHost == "" { + log.Fatal("SMTP_HOST must be set") + } + if SmtpPort == "" { + log.Fatal("SMTP_PORT must be set") + } + if SmtpUser == "" { + log.Fatal("SMTP_USER must be set") + } + if SmtpPass == "" { + log.Fatal("SMTP_PASS must be set") + } + if SmtpFromMail == "" { + log.Fatal("SMTP_FROM_MAIL must be set") + } + if SmtpFromName == "" { + log.Fatal("SMTP_FROM_NAME must be set") + } + if BaseUrl == "" { + log.Fatal("BASE_URL must be set") + } + + slog.Info("BASE_URL is " + BaseUrl) + slog.Info("SMTP_HOST is " + SmtpHost) + slog.Info("SMTP_PORT is " + SmtpPort) + slog.Info("SMTP_USER is " + SmtpUser) + slog.Info("SMTP_PASS is " + SmtpPass) + slog.Info("SMTP_FROM_MAIL is " + SmtpFromMail) + slog.Info("SMTP_FROM_NAME is " + SmtpFromName) +} diff --git a/utils/mail.go b/utils/mail.go new file mode 100644 index 0000000..1addb24 --- /dev/null +++ b/utils/mail.go @@ -0,0 +1,19 @@ +package utils + +import ( + "log/slog" + "net/smtp" +) + +func SendWelcomeMail(to string) { + + auth := smtp.PlainAuth("", SmtpUser, SmtpPass, SmtpHost) + + msg := "From: " + SmtpFromName + " <" + SmtpFromMail + ">\nTo: " + to + "\nSubject: Welcome to me-fit\n\nWelcome to me-fit!" + + err := smtp.SendMail(SmtpHost+":"+SmtpPort, auth, SmtpFromMail, []string{to}, []byte(msg)) + if err != nil { + slog.Error("Could not send mail: " + err.Error()) + } + +}