feat(mail): #132 unify env variables and send mails with smtp

This commit is contained in:
2024-09-05 17:54:39 +02:00
parent 0872728f3a
commit 5172a30781
7 changed files with 111 additions and 22 deletions

View File

@@ -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.

View File

@@ -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/"))))

31
main.go
View File

@@ -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)
}
}()
}

View File

@@ -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" {

View File

@@ -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)

57
utils/env.go Normal file
View File

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

19
utils/mail.go Normal file
View File

@@ -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())
}
}