[#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)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if app.TestMailer.LastToAddress.Address != "test@example.com" {
 | 
			
		||||
					t.Fatalf("[verification] Expected the email to be sent to %s, got %s", "test@example.com", app.TestMailer.LastToAddress.Address)
 | 
			
		||||
				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.LastMessage.To.Address)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !strings.Contains(app.TestMailer.LastHtmlBody, "Verify") {
 | 
			
		||||
					t.Fatalf("[verification] Expected to sent a verification email, got \n%v\n%v", app.TestMailer.LastHtmlSubject, app.TestMailer.LastHtmlBody)
 | 
			
		||||
				if !strings.Contains(app.TestMailer.LastMessage.HTML, "Verify") {
 | 
			
		||||
					t.Fatalf("[verification] Expected to sent a verification email, got \n%v\n%v", app.TestMailer.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			ExpectedStatus:  204,
 | 
			
		||||
| 
						 | 
				
			
			@ -334,12 +334,12 @@ func TestSettingsTestEmail(t *testing.T) {
 | 
			
		|||
					t.Fatalf("[password-reset] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if app.TestMailer.LastToAddress.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)
 | 
			
		||||
				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.LastMessage.To.Address)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !strings.Contains(app.TestMailer.LastHtmlBody, "Reset password") {
 | 
			
		||||
					t.Fatalf("[password-reset] Expected to sent a password-reset email, got \n%v\n%v", app.TestMailer.LastHtmlSubject, app.TestMailer.LastHtmlBody)
 | 
			
		||||
				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.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			ExpectedStatus:  204,
 | 
			
		||||
| 
						 | 
				
			
			@ -365,12 +365,12 @@ func TestSettingsTestEmail(t *testing.T) {
 | 
			
		|||
					t.Fatalf("[email-change] Expected 1 sent email, got %d", app.TestMailer.TotalSend)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if app.TestMailer.LastToAddress.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)
 | 
			
		||||
				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.LastMessage.To.Address)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !strings.Contains(app.TestMailer.LastHtmlBody, "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)
 | 
			
		||||
				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.LastMessage.Subject, app.TestMailer.LastMessage.HTML)
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			ExpectedStatus:  204,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,12 +35,14 @@ type ModelEvent struct {
 | 
			
		|||
 | 
			
		||||
type MailerRecordEvent struct {
 | 
			
		||||
	MailClient mailer.Mailer
 | 
			
		||||
	Message    *mailer.Message
 | 
			
		||||
	Record     *models.Record
 | 
			
		||||
	Meta       map[string]any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MailerAdminEvent struct {
 | 
			
		||||
	MailClient mailer.Mailer
 | 
			
		||||
	Message    *mailer.Message
 | 
			
		||||
	Admin      *models.Admin
 | 
			
		||||
	Meta       map[string]any
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,8 +72,8 @@ func TestEmailSendValidateAndSubmit(t *testing.T) {
 | 
			
		|||
			expectedContent = "Confirm new email"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !strings.Contains(app.TestMailer.LastHtmlBody, expectedContent) {
 | 
			
		||||
			t.Errorf("(%d) Expected the email to contains %s, got \n%v", i, expectedContent, app.TestMailer.LastHtmlBody)
 | 
			
		||||
		if !strings.Contains(app.TestMailer.LastMessage.HTML, expectedContent) {
 | 
			
		||||
			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/models"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tokens"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/mailer"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,29 +44,31 @@ func SendAdminPasswordReset(app core.App, admin *models.Admin) error {
 | 
			
		|||
 | 
			
		||||
	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{
 | 
			
		||||
		MailClient: mailClient,
 | 
			
		||||
		Message:    message,
 | 
			
		||||
		Admin:      admin,
 | 
			
		||||
		Meta:       map[string]any{"token": token},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error {
 | 
			
		||||
		// resolve body template
 | 
			
		||||
		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,
 | 
			
		||||
		)
 | 
			
		||||
		return e.MailClient.Send(e.Message)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if sendErr == nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,8 +30,8 @@ func TestSendAdminPasswordReset(t *testing.T) {
 | 
			
		|||
		"http://localhost:8090/_/#/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
 | 
			
		||||
	}
 | 
			
		||||
	for _, part := range expectedParts {
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
 | 
			
		||||
			t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
 | 
			
		||||
			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/mails/templates"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models/schema"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tokens"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/mailer"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
 | 
			
		||||
	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{
 | 
			
		||||
		MailClient: mailClient,
 | 
			
		||||
		Message:    message,
 | 
			
		||||
		Record:     authRecord,
 | 
			
		||||
		Meta:       map[string]any{"token": token},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error {
 | 
			
		||||
		settings := app.Settings()
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
		)
 | 
			
		||||
		return e.MailClient.Send(e.Message)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if sendErr == nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,30 +64,32 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error {
 | 
			
		|||
 | 
			
		||||
	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{
 | 
			
		||||
		MailClient: mailClient,
 | 
			
		||||
		Message:    message,
 | 
			
		||||
		Record:     authRecord,
 | 
			
		||||
		Meta:       map[string]any{"token": token},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error {
 | 
			
		||||
		settings := app.Settings()
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
		)
 | 
			
		||||
		return e.MailClient.Send(e.Message)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if sendErr == nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -104,8 +108,26 @@ func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string)
 | 
			
		|||
 | 
			
		||||
	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{
 | 
			
		||||
		MailClient: mailClient,
 | 
			
		||||
		Message:    message,
 | 
			
		||||
		Record:     record,
 | 
			
		||||
		Meta: map[string]any{
 | 
			
		||||
			"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 {
 | 
			
		||||
		settings := app.Settings()
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
		)
 | 
			
		||||
		return e.MailClient.Send(e.Message)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if sendErr == nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,8 +30,8 @@ func TestSendRecordPasswordReset(t *testing.T) {
 | 
			
		|||
		"http://localhost:8090/_/#/auth/confirm-password-reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.",
 | 
			
		||||
	}
 | 
			
		||||
	for _, part := range expectedParts {
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
 | 
			
		||||
			t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
 | 
			
		||||
			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.",
 | 
			
		||||
	}
 | 
			
		||||
	for _, part := range expectedParts {
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
 | 
			
		||||
			t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
 | 
			
		||||
			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.",
 | 
			
		||||
	}
 | 
			
		||||
	for _, part := range expectedParts {
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastHtmlBody, part) {
 | 
			
		||||
			t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastHtmlBody)
 | 
			
		||||
		if !strings.Contains(testApp.TestMailer.LastMessage.HTML, part) {
 | 
			
		||||
			t.Fatalf("Couldn't find %s \nin\n %s", part, testApp.TestMailer.LastMessage.HTML)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,6 @@
 | 
			
		|||
package tests
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/mailer"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,28 +8,20 @@ var _ mailer.Mailer = (*TestMailer)(nil)
 | 
			
		|||
 | 
			
		||||
// TestMailer is a mock `mailer.Mailer` implementation.
 | 
			
		||||
type TestMailer struct {
 | 
			
		||||
	TotalSend       int
 | 
			
		||||
	LastFromAddress mail.Address
 | 
			
		||||
	LastToAddress   mail.Address
 | 
			
		||||
	LastHtmlSubject string
 | 
			
		||||
	LastHtmlBody    string
 | 
			
		||||
	TotalSend   int
 | 
			
		||||
	LastMessage mailer.Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reset clears any previously test collected data.
 | 
			
		||||
func (m *TestMailer) Reset() {
 | 
			
		||||
	m.TotalSend = 0
 | 
			
		||||
	m.LastFromAddress = mail.Address{}
 | 
			
		||||
	m.LastToAddress = mail.Address{}
 | 
			
		||||
	m.LastHtmlSubject = ""
 | 
			
		||||
	m.LastHtmlBody = ""
 | 
			
		||||
	m.LastMessage = mailer.Message{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	m.LastFromAddress = fromEmail
 | 
			
		||||
	m.LastToAddress = toEmail
 | 
			
		||||
	m.LastHtmlSubject = subject
 | 
			
		||||
	m.LastHtmlBody = html
 | 
			
		||||
	m.TotalSend++
 | 
			
		||||
func (c *TestMailer) Send(m *mailer.Message) error {
 | 
			
		||||
	c.TotalSend++
 | 
			
		||||
	c.LastMessage = *m
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,14 +5,21 @@ import (
 | 
			
		|||
	"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.
 | 
			
		||||
type Mailer interface {
 | 
			
		||||
	// Send sends an email with HTML body to the specified recipient.
 | 
			
		||||
	Send(
 | 
			
		||||
		fromEmail mail.Address,
 | 
			
		||||
		toEmail mail.Address,
 | 
			
		||||
		subject string,
 | 
			
		||||
		htmlContent string,
 | 
			
		||||
		attachments map[string]io.Reader,
 | 
			
		||||
	) error
 | 
			
		||||
	// Send sends an email with the provided Message.
 | 
			
		||||
	Send(message *Message) error
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,8 @@ package mailer
 | 
			
		|||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,19 +18,11 @@ type Sendmail struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Send implements `mailer.Mailer` interface.
 | 
			
		||||
//
 | 
			
		||||
// 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 {
 | 
			
		||||
func (c *Sendmail) Send(m *Message) error {
 | 
			
		||||
	headers := make(http.Header)
 | 
			
		||||
	headers.Set("Subject", mime.QEncoding.Encode("utf-8", subject))
 | 
			
		||||
	headers.Set("From", fromEmail.String())
 | 
			
		||||
	headers.Set("To", toEmail.String())
 | 
			
		||||
	headers.Set("Subject", mime.QEncoding.Encode("utf-8", m.Subject))
 | 
			
		||||
	headers.Set("From", m.From.String())
 | 
			
		||||
	headers.Set("To", m.To.String())
 | 
			
		||||
	headers.Set("Content-Type", "text/html; charset=UTF-8")
 | 
			
		||||
 | 
			
		||||
	cmdPath, err := findSendmailPath()
 | 
			
		||||
| 
						 | 
				
			
			@ -50,12 +40,18 @@ func (m *Sendmail) Send(
 | 
			
		|||
	if _, err := buffer.Write([]byte("\r\n")); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := buffer.Write([]byte(htmlContent)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	if m.HTML != "" {
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
	return sendmail.Run()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,11 @@ package mailer
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/mail"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/domodwyer/mailyak/v3"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/security"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var _ Mailer = (*SmtpClient)(nil)
 | 
			
		||||
| 
						 | 
				
			
			@ -39,46 +39,72 @@ type SmtpClient struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Send implements `mailer.Mailer` interface.
 | 
			
		||||
func (m *SmtpClient) Send(
 | 
			
		||||
	fromEmail mail.Address,
 | 
			
		||||
	toEmail mail.Address,
 | 
			
		||||
	subject string,
 | 
			
		||||
	htmlContent string,
 | 
			
		||||
	attachments map[string]io.Reader,
 | 
			
		||||
) error {
 | 
			
		||||
func (c *SmtpClient) Send(m *Message) error {
 | 
			
		||||
	var smtpAuth smtp.Auth
 | 
			
		||||
	if m.username != "" || m.password != "" {
 | 
			
		||||
		smtpAuth = smtp.PlainAuth("", m.username, m.password, m.host)
 | 
			
		||||
	if c.username != "" || c.password != "" {
 | 
			
		||||
		smtpAuth = smtp.PlainAuth("", c.username, c.password, c.host)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create mail instance
 | 
			
		||||
	var yak *mailyak.MailYak
 | 
			
		||||
	if m.tls {
 | 
			
		||||
	if c.tls {
 | 
			
		||||
		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 {
 | 
			
		||||
			return tlsErr
 | 
			
		||||
		}
 | 
			
		||||
	} 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 != "" {
 | 
			
		||||
		yak.FromName(fromEmail.Name)
 | 
			
		||||
	if m.From.Name != "" {
 | 
			
		||||
		yak.FromName(m.From.Name)
 | 
			
		||||
	}
 | 
			
		||||
	yak.From(fromEmail.Address)
 | 
			
		||||
	yak.To(toEmail.Address)
 | 
			
		||||
	yak.Subject(subject)
 | 
			
		||||
	yak.HTML().Set(htmlContent)
 | 
			
		||||
	yak.From(m.From.Address)
 | 
			
		||||
	yak.To(m.To.Address)
 | 
			
		||||
	yak.Subject(m.Subject)
 | 
			
		||||
	yak.HTML().Set(m.HTML)
 | 
			
		||||
 | 
			
		||||
	// try to generate a plain text version of the HTML
 | 
			
		||||
	if plain, err := html2Text(htmlContent); err == nil {
 | 
			
		||||
		yak.Plain().Set(plain)
 | 
			
		||||
	if m.Text == "" {
 | 
			
		||||
		// try to generate a plain text version of the HTML
 | 
			
		||||
		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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue