package mails
import (
"html/template"
"net/mail"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/mails/templates"
"github.com/pocketbase/pocketbase/tools/mailer"
)
// SendRecordAuthAlert sends a new device login alert to the specified auth record.
func SendRecordAuthAlert(app core.App, authRecord *core.Record) error {
mailClient := app.NewMailClient()
subject, body, err := resolveEmailTemplate(app, authRecord, authRecord.Collection().AuthAlert.EmailTemplate, nil)
if err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: authRecord.Email()}},
Subject: subject,
HTML: body,
}
event := new(core.MailerRecordEvent)
event.App = app
event.Mailer = mailClient
event.Message = message
event.Record = authRecord
return app.OnMailerRecordAuthAlertSend().Trigger(event, func(e *core.MailerRecordEvent) error {
return e.Mailer.Send(e.Message)
})
}
// SendRecordOTP sends OTP email to the specified auth record.
//
// This method will also update the "sentTo" field of the related OTP record to the mail sent To address (if the OTP exists and not already assigned).
func SendRecordOTP(app core.App, authRecord *core.Record, otpId string, pass string) error {
mailClient := app.NewMailClient()
subject, body, err := resolveEmailTemplate(app, authRecord, authRecord.Collection().OTP.EmailTemplate, map[string]any{
core.EmailPlaceholderOTPId: otpId,
core.EmailPlaceholderOTP: pass,
})
if err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: authRecord.Email()}},
Subject: subject,
HTML: body,
}
event := new(core.MailerRecordEvent)
event.App = app
event.Mailer = mailClient
event.Message = message
event.Record = authRecord
event.Meta = map[string]any{
"otpId": otpId,
"password": pass,
}
return app.OnMailerRecordOTPSend().Trigger(event, func(e *core.MailerRecordEvent) error {
err := e.Mailer.Send(e.Message)
if err != nil {
return err
}
var toAddress string
if len(e.Message.To) > 0 {
toAddress = e.Message.To[0].Address
}
if toAddress == "" {
return nil
}
otp, err := e.App.FindOTPById(otpId)
if err != nil {
e.App.Logger().Warn(
"Unable to find OTP to update its sentTo field (either it was already deleted or the id is nonexisting)",
"error", err,
"otpId", otpId,
)
return nil
}
if otp.SentTo() != "" {
return nil // was already sent to another target
}
otp.SetSentTo(toAddress)
if err = e.App.Save(otp); err != nil {
e.App.Logger().Error(
"Failed to update OTP sentTo field",
"error", err,
"otpId", otpId,
"to", toAddress,
)
}
return nil
})
}
// SendRecordPasswordReset sends a password reset request email to the specified auth record.
func SendRecordPasswordReset(app core.App, authRecord *core.Record) error {
token, tokenErr := authRecord.NewPasswordResetToken()
if tokenErr != nil {
return tokenErr
}
mailClient := app.NewMailClient()
subject, body, err := resolveEmailTemplate(app, authRecord, authRecord.Collection().ResetPasswordTemplate, map[string]any{
core.EmailPlaceholderToken: token,
})
if err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: authRecord.Email()}},
Subject: subject,
HTML: body,
}
event := new(core.MailerRecordEvent)
event.App = app
event.Mailer = mailClient
event.Message = message
event.Record = authRecord
event.Meta = map[string]any{"token": token}
return app.OnMailerRecordPasswordResetSend().Trigger(event, func(e *core.MailerRecordEvent) error {
return e.Mailer.Send(e.Message)
})
}
// SendRecordVerification sends a verification request email to the specified auth record.
func SendRecordVerification(app core.App, authRecord *core.Record) error {
token, tokenErr := authRecord.NewVerificationToken()
if tokenErr != nil {
return tokenErr
}
mailClient := app.NewMailClient()
subject, body, err := resolveEmailTemplate(app, authRecord, authRecord.Collection().VerificationTemplate, map[string]any{
core.EmailPlaceholderToken: token,
})
if err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: authRecord.Email()}},
Subject: subject,
HTML: body,
}
event := new(core.MailerRecordEvent)
event.App = app
event.Mailer = mailClient
event.Message = message
event.Record = authRecord
event.Meta = map[string]any{"token": token}
return app.OnMailerRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error {
return e.Mailer.Send(e.Message)
})
}
// SendRecordChangeEmail sends a change email confirmation email to the specified auth record.
func SendRecordChangeEmail(app core.App, authRecord *core.Record, newEmail string) error {
token, tokenErr := authRecord.NewEmailChangeToken(newEmail)
if tokenErr != nil {
return tokenErr
}
mailClient := app.NewMailClient()
subject, body, err := resolveEmailTemplate(app, authRecord, authRecord.Collection().ConfirmEmailChangeTemplate, map[string]any{
core.EmailPlaceholderToken: token,
})
if err != nil {
return err
}
message := &mailer.Message{
From: mail.Address{
Name: app.Settings().Meta.SenderName,
Address: app.Settings().Meta.SenderAddress,
},
To: []mail.Address{{Address: newEmail}},
Subject: subject,
HTML: body,
}
event := new(core.MailerRecordEvent)
event.App = app
event.Mailer = mailClient
event.Message = message
event.Record = authRecord
event.Meta = map[string]any{
"token": token,
"newEmail": newEmail,
}
return app.OnMailerRecordEmailChangeSend().Trigger(event, func(e *core.MailerRecordEvent) error {
return e.Mailer.Send(e.Message)
})
}
func resolveEmailTemplate(
app core.App,
authRecord *core.Record,
emailTemplate core.EmailTemplate,
placeholders map[string]any,
) (subject string, body string, err error) {
if placeholders == nil {
placeholders = map[string]any{}
}
// register default system placeholders
if _, ok := placeholders[core.EmailPlaceholderAppName]; !ok {
placeholders[core.EmailPlaceholderAppName] = app.Settings().Meta.AppName
}
if _, ok := placeholders[core.EmailPlaceholderAppURL]; !ok {
placeholders[core.EmailPlaceholderAppURL] = app.Settings().Meta.AppURL
}
// register default auth record placeholders
for _, field := range authRecord.Collection().Fields {
if field.GetHidden() {
continue
}
fieldPlacehodler := "{RECORD:" + field.GetName() + "}"
if _, ok := placeholders[fieldPlacehodler]; !ok {
placeholders[fieldPlacehodler] = authRecord.Get(field.GetName())
}
}
subject, rawBody := emailTemplate.Resolve(placeholders)
params := struct {
HTMLContent template.HTML
}{
HTMLContent: template.HTML(rawBody),
}
body, err = resolveTemplateContent(params, templates.Layout, templates.HTMLBody)
if err != nil {
return "", "", err
}
return subject, body, nil
}