package mail import ( "bytes" "encoding/json" "fmt" "net" "net/http" "net/mail" "pocketbase/custom/cloudflare" "strings" "text/template" "time" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/router" ) type EmailData struct { Name string `json:"name"` Email string `json:"email"` Token string `json:"token"` Message string `json:"message"` Phone string `json:"phone"` LangIso string `json:"lang_iso"` } type MailsSettings struct { ReceiverMail string `json:"receiver_mail"` ReceiverName string `json:"receiver_name"` Subject string `json:"subject"` } func ServeMailSender(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] { return se.Router.POST("/api/email/send", func(e *core.RequestEvent) error { // name := e.Request.PathValue("name") data := EmailData{} if err := e.BindBody(&data); err != nil { e.Response.Header().Set("Content-Type", "application/json") app.Logger().Error(err.Error(), "type", "mail") return e.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } if pass, err := validateMX(data.Email); !pass && err != nil { e.Response.Header().Set("Content-Type", "application/json") app.Logger().Error("Invalid email address.", "type", "mail", "error", err.Error()) return e.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid email address."}) } // fmt.Printf("e.Request.Body: %v IP: %s\n", data, e.RealIP()) err := cloudflare.VerifyTurnstile(app, data.Token, e.RealIP()) if err != nil { e.Response.Header().Set("Content-Type", "application/json") app.Logger().Error("Captcha verification failed.", "type", "mail", "error", err.Error()) return e.JSON(http.StatusBadRequest, map[string]string{"error": "Captcha verification failed."}) } eamil_body, err := app.FindFirstRecordByFilter("email_template", fmt.Sprintf("name='contact_form'&&id_lang='%s'", data.LangIso), nil) if err != nil { e.Response.Header().Set("Content-Type", "application/json") app.Logger().Error("Template not available", "type", "mail", "error", err.Error()) return e.JSON(http.StatusBadRequest, map[string]string{"error": "Template not available"}) } templ, err := template.New("test").Parse(eamil_body.GetString("template")) if err != nil { app.Logger().Error("Template parsing error.", "type", "mail", "error", err.Error()) e.Response.Header().Set("Content-Type", "application/json") return e.JSON(http.StatusBadRequest, map[string]string{"error": "Template parsing error."}) } buf := bytes.Buffer{} templ.Execute(&buf, map[string]interface{}{ "Data": time.Now().Local().Format("2006-01-02 15:04:05"), "Name": data.Name, "Email": data.Email, "Message": strings.ReplaceAll(data.Message, "\n", "
"), "Phone": data.Phone, }) mailsSettings, err := getSettings(app) if err != nil { app.Logger().Error("Mails settings corrupted", "type", "mail", "error", err.Error()) e.Response.Header().Set("Content-Type", "application/json") return e.JSON(http.StatusBadRequest, map[string]string{"error": "Mails settings corrupted"}) } cc := []mail.Address{{Address: data.Email, Name: data.Name}} to := []mail.Address{{Address: mailsSettings.ReceiverMail, Name: mailsSettings.ReceiverName}} message := &mailer.Message{ From: mail.Address{ Address: e.App.Settings().Meta.SenderAddress, Name: e.App.Settings().Meta.SenderName, }, Cc: cc, To: to, Subject: mailsSettings.Subject, HTML: buf.String(), } err = e.App.NewMailClient().Send(message) if err != nil { app.Logger().Error("Mails sending error", "type", "mail", "error", err.Error()) e.Response.Header().Set("Content-Type", "application/json") return e.JSON(http.StatusBadRequest, map[string]string{"error": "Mails sending error"}) } receivers := formReceiversList(to, cc, []mail.Address{}) app.Logger().Info("Mail Sent", "type", "mail", "receivers", receivers) return e.JSON(http.StatusOK, map[string]string{"status": "success", "message": "Email sent successfully. to " + receivers}) }) } func formReceiversList(to []mail.Address, cc []mail.Address, bcc []mail.Address) string { res := "" for _, a := range to { res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address) } for _, a := range cc { res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address) } for _, a := range bcc { res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address) } return res } func getSettings(app *pocketbase.PocketBase) (*MailsSettings, error) { record, err := app.FindFirstRecordByFilter("settings", "key='contact_page'", nil) settings := MailsSettings{} json.Unmarshal([]byte(record.GetString("value")), &settings) if err != nil { return nil, err } return &settings, nil } func extractDomain(email string) string { parts := strings.Split(email, "@") if len(parts) != 2 { return "" } return strings.TrimSpace(parts[1]) } // validateMX checks if the domain has valid MX records or A records as a fallback func validateMX(email string) (bool, error) { // Check MX records domain := extractDomain(email) mxRecords, err := net.LookupMX(domain) if err != nil { return false, fmt.Errorf("'MX' records for %s not found", domain) } else if len(mxRecords) > 0 { // At least one MX record exists return true, nil } // Fallback: Check for A records (some domains accept mail via A records) aRecords, err := net.LookupHost(domain) if err != nil { return false, fmt.Errorf("'A' record for %s not found", domain) } return len(aRecords) > 0, nil }