[#1069] added default Message-ID and more options to customize the mail message
This commit is contained in:
parent
c4a660d2d2
commit
3e1a19685b
|
@ -303,12 +303,12 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||||
t.Fatalf("[verification] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
t.Fatalf("[verification] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.TestMailer.LastToAddress.Address != "test@example.com" {
|
if app.TestMailer.LastMessage.To.Address != "test@example.com" {
|
||||||
t.Fatalf("[verification] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastToAddress.Address)
|
t.Fatalf("[verification] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastMessage.To.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(app.TestMailer.LastHtmlBody, "Verify") {
|
if !strings.Contains(app.TestMailer.LastMessage.HTML, "Verify") {
|
||||||
t.Fatalf("[verification] Expected to sent a verification email, got \n%v\n%v", app.TestMailer.LastHtmlSubject, app.TestMailer.LastHtmlBody)
|
t.Fatalf("[verification] Expected to sent a verification email, got \n%v\n%v", app.TestMailer.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExpectedStatus: 204,
|
ExpectedStatus: 204,
|
||||||
|
@ -334,12 +334,12 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||||
t.Fatalf("[password-reset] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
t.Fatalf("[password-reset] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.TestMailer.LastToAddress.Address != "test@example.com" {
|
if app.TestMailer.LastMessage.To.Address != "test@example.com" {
|
||||||
t.Fatalf("[password-reset] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastToAddress.Address)
|
t.Fatalf("[password-reset] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastMessage.To.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(app.TestMailer.LastHtmlBody, "Reset password") {
|
if !strings.Contains(app.TestMailer.LastMessage.HTML, "Reset password") {
|
||||||
t.Fatalf("[password-reset] Expected to sent a password-reset email, got \n%v\n%v", app.TestMailer.LastHtmlSubject, app.TestMailer.LastHtmlBody)
|
t.Fatalf("[password-reset] Expected to sent a password-reset email, got \n%v\n%v", app.TestMailer.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExpectedStatus: 204,
|
ExpectedStatus: 204,
|
||||||
|
@ -365,12 +365,12 @@ func TestSettingsTestEmail(t *testing.T) {
|
||||||
t.Fatalf("[email-change] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
t.Fatalf("[email-change] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.TestMailer.LastToAddress.Address != "test@example.com" {
|
if app.TestMailer.LastMessage.To.Address != "test@example.com" {
|
||||||
t.Fatalf("[email-change] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastToAddress.Address)
|
t.Fatalf("[email-change] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastMessage.To.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(app.TestMailer.LastHtmlBody, "Confirm new email") {
|
if !strings.Contains(app.TestMailer.LastMessage.HTML, "Confirm new email") {
|
||||||
t.Fatalf("[email-change] Expected to sent a confirm new email email, got \n%v\n%v", app.TestMailer.LastHtmlSubject, app.TestMailer.LastHtmlBody)
|
t.Fatalf("[email-change] Expected to sent a confirm new email email, got \n%v\n%v", app.TestMailer.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ExpectedStatus: 204,
|
ExpectedStatus: 204,
|
||||||
|
|
|
@ -35,12 +35,14 @@ type ModelEvent struct {
|
||||||
|
|
||||||
type MailerRecordEvent struct {
|
type MailerRecordEvent struct {
|
||||||
MailClient mailer.Mailer
|
MailClient mailer.Mailer
|
||||||
|
Message *mailer.Message
|
||||||
Record *models.Record
|
Record *models.Record
|
||||||
Meta map[string]any
|
Meta map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
type MailerAdminEvent struct {
|
type MailerAdminEvent struct {
|
||||||
MailClient mailer.Mailer
|
MailClient mailer.Mailer
|
||||||
|
Message *mailer.Message
|
||||||
Admin *models.Admin
|
Admin *models.Admin
|
||||||
Meta map[string]any
|
Meta map[string]any
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,8 +72,8 @@ func TestEmailSendValidateAndSubmit(t *testing.T) {
|
||||||
expectedContent = "Confirm new email"
|
expectedContent = "Confirm new email"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(app.TestMailer.LastHtmlBody, expectedContent) {
|
if !strings.Contains(app.TestMailer.LastMessage.HTML, expectedContent) {
|
||||||
t.Errorf("(%d) Expected the email to contains %s, got \n%v", i, expectedContent, app.TestMailer.LastHtmlBody)
|
t.Errorf("(%d) Expected the email to contains %s, got \n%v", i, expectedContent, app.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/pocketbase/pocketbase/mails/templates"
|
"github.com/pocketbase/pocketbase/mails/templates"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/tokens"
|
"github.com/pocketbase/pocketbase/tokens"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||||
"github.com/pocketbase/pocketbase/tools/rest"
|
"github.com/pocketbase/pocketbase/tools/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,29 +44,31 @@ func SendAdminPasswordReset(app core.App, admin *models.Admin) error {
|
||||||
|
|
||||||
mailClient := app.NewMailClient()
|
mailClient := app.NewMailClient()
|
||||||
|
|
||||||
|
// resolve body template
|
||||||
|
body, renderErr := resolveTemplateContent(params, templates.Layout, templates.AdminPasswordResetBody)
|
||||||
|
if renderErr != nil {
|
||||||
|
return renderErr
|
||||||
|
}
|
||||||
|
|
||||||
|
message := &mailer.Message{
|
||||||
|
From: mail.Address{
|
||||||
|
Name: app.Settings().Meta.SenderName,
|
||||||
|
Address: app.Settings().Meta.SenderAddress,
|
||||||
|
},
|
||||||
|
To: mail.Address{Address: admin.Email},
|
||||||
|
Subject: "Reset admin password",
|
||||||
|
HTML: body,
|
||||||
|
}
|
||||||
|
|
||||||
event := &core.MailerAdminEvent{
|
event := &core.MailerAdminEvent{
|
||||||
MailClient: mailClient,
|
MailClient: mailClient,
|
||||||
|
Message: message,
|
||||||
Admin: admin,
|
Admin: admin,
|
||||||
Meta: map[string]any{"token": token},
|
Meta: map[string]any{"token": token},
|
||||||
}
|
}
|
||||||
|
|
||||||
sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error {
|
sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error {
|
||||||
// resolve body template
|
return e.MailClient.Send(e.Message)
|
||||||
body, renderErr := resolveTemplateContent(params, templates.Layout, templates.AdminPasswordResetBody)
|
|
||||||
if renderErr != nil {
|
|
||||||
return renderErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.MailClient.Send(
|
|
||||||
mail.Address{
|
|
||||||
Name: app.Settings().Meta.SenderName,
|
|
||||||
Address: app.Settings().Meta.SenderAddress,
|
|
||||||
},
|
|
||||||
mail.Address{Address: e.Admin.Email},
|
|
||||||
"Reset admin password",
|
|
||||||
body,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if sendErr == nil {
|
if sendErr == nil {
|
||||||
|
|
|
@ -30,8 +30,8 @@ func TestSendAdminPasswordReset(t *testing.T) {
|
||||||
"http://localhost:8090/_/#/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
"http://localhost:8090/_/#/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
||||||
}
|
}
|
||||||
for _, part := range expectedParts {
|
for _, part := range expectedParts {
|
||||||
if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
|
if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
|
||||||
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
|
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
mails/record.go
110
mails/record.go
|
@ -7,8 +7,8 @@ import (
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/mails/templates"
|
"github.com/pocketbase/pocketbase/mails/templates"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/models/schema"
|
|
||||||
"github.com/pocketbase/pocketbase/tokens"
|
"github.com/pocketbase/pocketbase/tokens"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendRecordPasswordReset sends a password reset request email to the specified user.
|
// SendRecordPasswordReset sends a password reset request email to the specified user.
|
||||||
|
@ -20,30 +20,32 @@ func SendRecordPasswordReset(app core.App, authRecord *models.Record) error {
|
||||||
|
|
||||||
mailClient := app.NewMailClient()
|
mailClient := app.NewMailClient()
|
||||||
|
|
||||||
|
settings := app.Settings()
|
||||||
|
|
||||||
|
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.ResetPasswordTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := &mailer.Message{
|
||||||
|
From: mail.Address{
|
||||||
|
Name: settings.Meta.SenderName,
|
||||||
|
Address: settings.Meta.SenderAddress,
|
||||||
|
},
|
||||||
|
To: mail.Address{Address: authRecord.Email()},
|
||||||
|
Subject: subject,
|
||||||
|
HTML: body,
|
||||||
|
}
|
||||||
|
|
||||||
event := &core.MailerRecordEvent{
|
event := &core.MailerRecordEvent{
|
||||||
MailClient: mailClient,
|
MailClient: mailClient,
|
||||||
|
Message: message,
|
||||||
Record: authRecord,
|
Record: authRecord,
|
||||||
Meta: map[string]any{"token": token},
|
Meta: map[string]any{"token": token},
|
||||||
}
|
}
|
||||||
|
|
||||||
sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
||||||
settings := app.Settings()
|
return e.MailClient.Send(e.Message)
|
||||||
|
|
||||||
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.ResetPasswordTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.MailClient.Send(
|
|
||||||
mail.Address{
|
|
||||||
Name: settings.Meta.SenderName,
|
|
||||||
Address: settings.Meta.SenderAddress,
|
|
||||||
},
|
|
||||||
mail.Address{Address: e.Record.GetString(schema.FieldNameEmail)},
|
|
||||||
subject,
|
|
||||||
body,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if sendErr == nil {
|
if sendErr == nil {
|
||||||
|
@ -62,30 +64,32 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error {
|
||||||
|
|
||||||
mailClient := app.NewMailClient()
|
mailClient := app.NewMailClient()
|
||||||
|
|
||||||
|
settings := app.Settings()
|
||||||
|
|
||||||
|
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.VerificationTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := &mailer.Message{
|
||||||
|
From: mail.Address{
|
||||||
|
Name: settings.Meta.SenderName,
|
||||||
|
Address: settings.Meta.SenderAddress,
|
||||||
|
},
|
||||||
|
To: mail.Address{Address: authRecord.Email()},
|
||||||
|
Subject: subject,
|
||||||
|
HTML: body,
|
||||||
|
}
|
||||||
|
|
||||||
event := &core.MailerRecordEvent{
|
event := &core.MailerRecordEvent{
|
||||||
MailClient: mailClient,
|
MailClient: mailClient,
|
||||||
|
Message: message,
|
||||||
Record: authRecord,
|
Record: authRecord,
|
||||||
Meta: map[string]any{"token": token},
|
Meta: map[string]any{"token": token},
|
||||||
}
|
}
|
||||||
|
|
||||||
sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
||||||
settings := app.Settings()
|
return e.MailClient.Send(e.Message)
|
||||||
|
|
||||||
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.VerificationTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.MailClient.Send(
|
|
||||||
mail.Address{
|
|
||||||
Name: settings.Meta.SenderName,
|
|
||||||
Address: settings.Meta.SenderAddress,
|
|
||||||
},
|
|
||||||
mail.Address{Address: e.Record.GetString(schema.FieldNameEmail)},
|
|
||||||
subject,
|
|
||||||
body,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if sendErr == nil {
|
if sendErr == nil {
|
||||||
|
@ -104,8 +108,26 @@ func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string)
|
||||||
|
|
||||||
mailClient := app.NewMailClient()
|
mailClient := app.NewMailClient()
|
||||||
|
|
||||||
|
settings := app.Settings()
|
||||||
|
|
||||||
|
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.ConfirmEmailChangeTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := &mailer.Message{
|
||||||
|
From: mail.Address{
|
||||||
|
Name: settings.Meta.SenderName,
|
||||||
|
Address: settings.Meta.SenderAddress,
|
||||||
|
},
|
||||||
|
To: mail.Address{Address: newEmail},
|
||||||
|
Subject: subject,
|
||||||
|
HTML: body,
|
||||||
|
}
|
||||||
|
|
||||||
event := &core.MailerRecordEvent{
|
event := &core.MailerRecordEvent{
|
||||||
MailClient: mailClient,
|
MailClient: mailClient,
|
||||||
|
Message: message,
|
||||||
Record: record,
|
Record: record,
|
||||||
Meta: map[string]any{
|
Meta: map[string]any{
|
||||||
"token": token,
|
"token": token,
|
||||||
|
@ -114,23 +136,7 @@ func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendErr := app.OnMailerBeforeRecordChangeEmailSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
sendErr := app.OnMailerBeforeRecordChangeEmailSend().Trigger(event, func(e *core.MailerRecordEvent) error {
|
||||||
settings := app.Settings()
|
return e.MailClient.Send(e.Message)
|
||||||
|
|
||||||
subject, body, err := resolveEmailTemplate(app, token, settings.Meta.ConfirmEmailChangeTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.MailClient.Send(
|
|
||||||
mail.Address{
|
|
||||||
Name: settings.Meta.SenderName,
|
|
||||||
Address: settings.Meta.SenderAddress,
|
|
||||||
},
|
|
||||||
mail.Address{Address: newEmail},
|
|
||||||
subject,
|
|
||||||
body,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if sendErr == nil {
|
if sendErr == nil {
|
||||||
|
|
|
@ -30,8 +30,8 @@ func TestSendRecordPasswordReset(t *testing.T) {
|
||||||
"http://localhost:8090/_/#/auth/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
"http://localhost:8090/_/#/auth/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
||||||
}
|
}
|
||||||
for _, part := range expectedParts {
|
for _, part := range expectedParts {
|
||||||
if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
|
if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
|
||||||
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
|
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,8 @@ func TestSendRecordVerification(t *testing.T) {
|
||||||
"http://localhost:8090/_/#/auth/confirm-verification/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
"http://localhost:8090/_/#/auth/confirm-verification/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
||||||
}
|
}
|
||||||
for _, part := range expectedParts {
|
for _, part := range expectedParts {
|
||||||
if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
|
if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
|
||||||
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
|
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,8 @@ func TestSendRecordChangeEmail(t *testing.T) {
|
||||||
"http://localhost:8090/_/#/auth/confirm-email-change/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
"http://localhost:8090/_/#/auth/confirm-email-change/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
|
||||||
}
|
}
|
||||||
for _, part := range expectedParts {
|
for _, part := range expectedParts {
|
||||||
if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
|
if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
|
||||||
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
|
t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/mail"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,28 +8,20 @@ var _ mailer.Mailer = (*TestMailer)(nil)
|
||||||
|
|
||||||
// TestMailer is a mock `mailer.Mailer` implementation.
|
// TestMailer is a mock `mailer.Mailer` implementation.
|
||||||
type TestMailer struct {
|
type TestMailer struct {
|
||||||
TotalSend int
|
TotalSend int
|
||||||
LastFromAddress mail.Address
|
LastMessage mailer.Message
|
||||||
LastToAddress mail.Address
|
|
||||||
LastHtmlSubject string
|
|
||||||
LastHtmlBody string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset clears any previously test collected data.
|
// Reset clears any previously test collected data.
|
||||||
func (m *TestMailer) Reset() {
|
func (m *TestMailer) Reset() {
|
||||||
m.TotalSend = 0
|
m.TotalSend = 0
|
||||||
m.LastFromAddress = mail.Address{}
|
m.LastMessage = mailer.Message{}
|
||||||
m.LastToAddress = mail.Address{}
|
|
||||||
m.LastHtmlSubject = ""
|
|
||||||
m.LastHtmlBody = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send implements `mailer.Mailer` interface.
|
// Send implements `mailer.Mailer` interface.
|
||||||
func (m *TestMailer) Send(fromEmail mail.Address, toEmail mail.Address, subject string, html string, attachments map[string]io.Reader) error {
|
func (c *TestMailer) Send(m *mailer.Message) error {
|
||||||
m.LastFromAddress = fromEmail
|
c.TotalSend++
|
||||||
m.LastToAddress = toEmail
|
c.LastMessage = *m
|
||||||
m.LastHtmlSubject = subject
|
|
||||||
m.LastHtmlBody = html
|
|
||||||
m.TotalSend++
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,21 @@ import (
|
||||||
"net/mail"
|
"net/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Message defines a generic email message struct.
|
||||||
|
type Message struct {
|
||||||
|
From mail.Address
|
||||||
|
To mail.Address
|
||||||
|
Bcc []string
|
||||||
|
Cc []string
|
||||||
|
Subject string
|
||||||
|
HTML string
|
||||||
|
Text string
|
||||||
|
Headers map[string]string
|
||||||
|
Attachments map[string]io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
// Mailer defines a base mail client interface.
|
// Mailer defines a base mail client interface.
|
||||||
type Mailer interface {
|
type Mailer interface {
|
||||||
// Send sends an email with HTML body to the specified recipient.
|
// Send sends an email with the provided Message.
|
||||||
Send(
|
Send(message *Message) error
|
||||||
fromEmail mail.Address,
|
|
||||||
toEmail mail.Address,
|
|
||||||
subject string,
|
|
||||||
htmlContent string,
|
|
||||||
attachments map[string]io.Reader,
|
|
||||||
) error
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,8 @@ package mailer
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,19 +18,11 @@ type Sendmail struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send implements `mailer.Mailer` interface.
|
// Send implements `mailer.Mailer` interface.
|
||||||
//
|
func (c *Sendmail) Send(m *Message) error {
|
||||||
// Attachments are currently not supported.
|
|
||||||
func (m *Sendmail) Send(
|
|
||||||
fromEmail mail.Address,
|
|
||||||
toEmail mail.Address,
|
|
||||||
subject string,
|
|
||||||
htmlContent string,
|
|
||||||
attachments map[string]io.Reader,
|
|
||||||
) error {
|
|
||||||
headers := make(http.Header)
|
headers := make(http.Header)
|
||||||
headers.Set("Subject", mime.QEncoding.Encode("utf-8", subject))
|
headers.Set("Subject", mime.QEncoding.Encode("utf-8", m.Subject))
|
||||||
headers.Set("From", fromEmail.String())
|
headers.Set("From", m.From.String())
|
||||||
headers.Set("To", toEmail.String())
|
headers.Set("To", m.To.String())
|
||||||
headers.Set("Content-Type", "text/html; charset=UTF-8")
|
headers.Set("Content-Type", "text/html; charset=UTF-8")
|
||||||
|
|
||||||
cmdPath, err := findSendmailPath()
|
cmdPath, err := findSendmailPath()
|
||||||
|
@ -50,12 +40,18 @@ func (m *Sendmail) Send(
|
||||||
if _, err := buffer.Write([]byte("\r\n")); err != nil {
|
if _, err := buffer.Write([]byte("\r\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := buffer.Write([]byte(htmlContent)); err != nil {
|
if m.HTML != "" {
|
||||||
return err
|
if _, err := buffer.Write([]byte(m.HTML)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := buffer.Write([]byte(m.Text)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
sendmail := exec.Command(cmdPath, toEmail.Address)
|
sendmail := exec.Command(cmdPath, m.To.Address)
|
||||||
sendmail.Stdin = &buffer
|
sendmail.Stdin = &buffer
|
||||||
|
|
||||||
return sendmail.Run()
|
return sendmail.Run()
|
||||||
|
|
|
@ -2,11 +2,11 @@ package mailer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/mail"
|
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/domodwyer/mailyak/v3"
|
"github.com/domodwyer/mailyak/v3"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Mailer = (*SmtpClient)(nil)
|
var _ Mailer = (*SmtpClient)(nil)
|
||||||
|
@ -39,46 +39,72 @@ type SmtpClient struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send implements `mailer.Mailer` interface.
|
// Send implements `mailer.Mailer` interface.
|
||||||
func (m *SmtpClient) Send(
|
func (c *SmtpClient) Send(m *Message) error {
|
||||||
fromEmail mail.Address,
|
|
||||||
toEmail mail.Address,
|
|
||||||
subject string,
|
|
||||||
htmlContent string,
|
|
||||||
attachments map[string]io.Reader,
|
|
||||||
) error {
|
|
||||||
var smtpAuth smtp.Auth
|
var smtpAuth smtp.Auth
|
||||||
if m.username != "" || m.password != "" {
|
if c.username != "" || c.password != "" {
|
||||||
smtpAuth = smtp.PlainAuth("", m.username, m.password, m.host)
|
smtpAuth = smtp.PlainAuth("", c.username, c.password, c.host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create mail instance
|
// create mail instance
|
||||||
var yak *mailyak.MailYak
|
var yak *mailyak.MailYak
|
||||||
if m.tls {
|
if c.tls {
|
||||||
var tlsErr error
|
var tlsErr error
|
||||||
yak, tlsErr = mailyak.NewWithTLS(fmt.Sprintf("%s:%d", m.host, m.port), smtpAuth, nil)
|
yak, tlsErr = mailyak.NewWithTLS(fmt.Sprintf("%s:%d", c.host, c.port), smtpAuth, nil)
|
||||||
if tlsErr != nil {
|
if tlsErr != nil {
|
||||||
return tlsErr
|
return tlsErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yak = mailyak.New(fmt.Sprintf("%s:%d", m.host, m.port), smtpAuth)
|
yak = mailyak.New(fmt.Sprintf("%s:%d", c.host, c.port), smtpAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fromEmail.Name != "" {
|
if m.From.Name != "" {
|
||||||
yak.FromName(fromEmail.Name)
|
yak.FromName(m.From.Name)
|
||||||
}
|
}
|
||||||
yak.From(fromEmail.Address)
|
yak.From(m.From.Address)
|
||||||
yak.To(toEmail.Address)
|
yak.To(m.To.Address)
|
||||||
yak.Subject(subject)
|
yak.Subject(m.Subject)
|
||||||
yak.HTML().Set(htmlContent)
|
yak.HTML().Set(m.HTML)
|
||||||
|
|
||||||
// try to generate a plain text version of the HTML
|
if m.Text == "" {
|
||||||
if plain, err := html2Text(htmlContent); err == nil {
|
// try to generate a plain text version of the HTML
|
||||||
yak.Plain().Set(plain)
|
if plain, err := html2Text(m.HTML); err == nil {
|
||||||
|
yak.Plain().Set(plain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yak.Plain().Set(m.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, data := range attachments {
|
if len(m.Bcc) > 0 {
|
||||||
|
yak.Bcc(m.Bcc...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Cc) > 0 {
|
||||||
|
yak.Cc(m.Cc...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add attachements (if any)
|
||||||
|
for name, data := range m.Attachments {
|
||||||
yak.Attach(name, data)
|
yak.Attach(name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add custom headers (if any)
|
||||||
|
var hasMessageId bool
|
||||||
|
for k, v := range m.Headers {
|
||||||
|
if strings.EqualFold(k, "Message-ID") {
|
||||||
|
hasMessageId = true
|
||||||
|
}
|
||||||
|
yak.AddHeader(k, v)
|
||||||
|
}
|
||||||
|
if !hasMessageId {
|
||||||
|
// add a default message id if missing
|
||||||
|
fromParts := strings.Split(m.From.Address, "@")
|
||||||
|
if len(fromParts) == 2 {
|
||||||
|
yak.AddHeader("Message-ID", fmt.Sprintf("<%s@%s>",
|
||||||
|
security.PseudorandomString(15),
|
||||||
|
fromParts[1],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return yak.Send()
|
return yak.Send()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue