176 lines
5.6 KiB
Go
176 lines
5.6 KiB
Go
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", "<br>"),
|
|
"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
|
|
}
|