Skip to main content
PocketBase provides a mailer interface for sending emails using SMTP or Sendmail.

Creating mail clients

NewMailClient()

Create a mail client based on current app settings:
mailer := app.NewMailClient()
The mailer is automatically configured based on your app’s email settings (configured via the Admin UI).

Sending emails

Basic email

import (
    "net/mail"
    "github.com/pocketbase/pocketbase/tools/mailer"
)

mailer := app.NewMailClient()

message := &mailer.Message{
    From: mail.Address{
        Name:    "My App",
        Address: "noreply@example.com",
    },
    To: []mail.Address{
        {Address: "user@example.com"},
    },
    Subject: "Welcome to My App",
    HTML:    "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
}

if err := mailer.Send(message); err != nil {
    log.Fatal(err)
}

Email with text alternative

message := &mailer.Message{
    From: mail.Address{
        Address: "noreply@example.com",
    },
    To: []mail.Address{
        {Address: "user@example.com"},
    },
    Subject: "Welcome",
    HTML:    "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
    Text:    "Welcome! Thanks for signing up.",
}

mailer.Send(message)

Multiple recipients

message := &mailer.Message{
    From: mail.Address{
        Name:    "Newsletter",
        Address: "newsletter@example.com",
    },
    To: []mail.Address{
        {Address: "user1@example.com"},
        {Address: "user2@example.com"},
        {Address: "user3@example.com"},
    },
    Cc: []mail.Address{
        {Address: "manager@example.com"},
    },
    Bcc: []mail.Address{
        {Address: "admin@example.com"},
    },
    Subject: "Monthly Newsletter",
    HTML:    "<h1>Newsletter content</h1>",
}

mailer.Send(message)

Email attachments

Add file attachments

import "os"

file, err := os.Open("invoice.pdf")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

message := &mailer.Message{
    From:    mail.Address{Address: "billing@example.com"},
    To:      []mail.Address{{Address: "customer@example.com"}},
    Subject: "Your Invoice",
    HTML:    "<p>Please find your invoice attached.</p>",
    Attachments: map[string]io.Reader{
        "invoice.pdf": file,
    },
}

mailer.Send(message)

Multiple attachments

file1, _ := os.Open("document1.pdf")
defer file1.Close()

file2, _ := os.Open("document2.pdf")
defer file2.Close()

message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Your Documents",
    HTML:    "<p>Your requested documents are attached.</p>",
    Attachments: map[string]io.Reader{
        "document1.pdf": file1,
        "document2.pdf": file2,
    },
}

mailer.Send(message)

Inline attachments (embedded images)

image, _ := os.Open("logo.png")
defer image.Close()

message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Welcome Email",
    HTML:    `<h1>Welcome!</h1><img src="cid:logo.png" alt="Logo">`,
    InlineAttachments: map[string]io.Reader{
        "logo.png": image,
    },
}

mailer.Send(message)
Inline attachments can be referenced in HTML using cid:filename where filename matches the key in InlineAttachments.

Custom headers

Add custom email headers:
message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Test Email",
    HTML:    "<p>Test content</p>",
    Headers: map[string]string{
        "X-Custom-Header": "custom-value",
        "Reply-To":        "support@example.com",
        "X-Priority":      "1",
    },
}

mailer.Send(message)

Email templates

Using Go templates

import "html/template"

type EmailData struct {
    Name  string
    Token string
    URL   string
}

tmpl := template.Must(template.New("welcome").Parse(`
<!DOCTYPE html>
<html>
<body>
    <h1>Welcome {{.Name}}!</h1>
    <p>Please verify your email by clicking the link below:</p>
    <a href="{{.URL}}/verify?token={{.Token}}">Verify Email</a>
</body>
</html>
`))

var buf bytes.Buffer
err := tmpl.Execute(&buf, EmailData{
    Name:  "John Doe",
    Token: "verification-token",
    URL:   "https://example.com",
})
if err != nil {
    log.Fatal(err)
}

message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Verify Your Email",
    HTML:    buf.String(),
}

mailer.Send(message)

Using PocketBase templates

import "github.com/pocketbase/pocketbase/tools/template"

registry := template.NewRegistry()

html, err := registry.LoadFiles(
    "templates/email/welcome.html",
).Render(map[string]any{
    "name": "John Doe",
})
if err != nil {
    log.Fatal(err)
}

message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Welcome",
    HTML:    html,
}

mailer.Send(message)

Intercepting emails

OnMailerSend hook

Intercept all outgoing emails:
app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error {
    log.Printf("Sending email to: %v", e.Message.To)
    log.Printf("Subject: %s", e.Message.Subject)
    
    // Continue sending
    return e.Next()
})

Modify emails before sending

app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error {
    // Add a custom header to all emails
    if e.Message.Headers == nil {
        e.Message.Headers = make(map[string]string)
    }
    e.Message.Headers["X-App-Version"] = "1.0.0"
    
    // Add BCC to all emails
    e.Message.Bcc = append(e.Message.Bcc, mail.Address{
        Address: "archive@example.com",
    })
    
    return e.Next()
})

Block emails in development

app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error {
    if e.App.IsDev() {
        log.Printf("[DEV] Email blocked: %s", e.Message.Subject)
        log.Printf("[DEV] To: %v", e.Message.To)
        return nil // Don't call e.Next() to prevent sending
    }
    return e.Next()
})

OnMailerRecordVerificationSend

Intercept verification emails:
app.OnMailerRecordVerificationSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
    // Customize verification email
    e.Message.Subject = "Please verify your email address"
    
    // Access the record
    log.Printf("Sending verification to: %s", e.Record.Email())
    
    return e.Next()
})

OnMailerRecordPasswordResetSend

Intercept password reset emails:
app.OnMailerRecordPasswordResetSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
    e.Message.Subject = "Reset your password"
    return e.Next()
})

OnMailerRecordEmailChangeSend

Intercept email change confirmation emails:
app.OnMailerRecordEmailChangeSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
    log.Printf("Email change requested for user: %s", e.Record.Id)
    return e.Next()
})

OnMailerRecordAuthAlertSend

Intercept auth alert emails (new login notifications):
app.OnMailerRecordAuthAlertSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
    e.Message.Subject = "Security Alert: New Login Detected"
    return e.Next()
})

Sending custom emails from hooks

Welcome email on user registration

app.OnRecordAfterCreateSuccess("users").BindFunc(func(e *core.RecordEvent) error {
    mailer := e.App.NewMailClient()
    
    message := &mailer.Message{
        From: mail.Address{
            Name:    "My App",
            Address: "noreply@example.com",
        },
        To: []mail.Address{
            {Address: e.Record.Email()},
        },
        Subject: "Welcome to My App!",
        HTML: fmt.Sprintf(
            "<h1>Welcome %s!</h1><p>Thanks for joining.</p>",
            e.Record.GetString("name"),
        ),
    }
    
    if err := mailer.Send(message); err != nil {
        // Log error but don't fail the request
        e.App.Logger().Error("Failed to send welcome email", "error", err)
    }
    
    return e.Next()
})

Notification on record creation

app.OnRecordAfterCreateSuccess("posts").BindFunc(func(e *core.RecordEvent) error {
    // Get all users who should be notified
    users, err := e.App.FindRecordsByFilter(
        "users",
        "notifications = true",
        "",
        100,
        0,
    )
    if err != nil {
        return err
    }
    
    mailer := e.App.NewMailClient()
    
    for _, user := range users {
        message := &mailer.Message{
            From:    mail.Address{Address: "noreply@example.com"},
            To:      []mail.Address{{Address: user.Email()}},
            Subject: "New Post Published",
            HTML: fmt.Sprintf(
                "<p>A new post '%s' has been published.</p>",
                e.Record.GetString("title"),
            ),
        }
        
        if err := mailer.Send(message); err != nil {
            e.App.Logger().Error("Failed to send notification", "error", err)
        }
    }
    
    return e.Next()
})

HTML to text conversion

PocketBase can automatically convert HTML to plain text:
import "github.com/pocketbase/pocketbase/tools/mailer"

html := "<h1>Hello</h1><p>This is a <strong>test</strong></p>"
text := mailer.StripHTML(html)
// Result: "Hello\nThis is a test"

message := &mailer.Message{
    From:    mail.Address{Address: "noreply@example.com"},
    To:      []mail.Address{{Address: "user@example.com"}},
    Subject: "Test",
    HTML:    html,
    Text:    text, // Fallback for email clients that don't support HTML
}

Message type signature

type Message struct {
    From              mail.Address         // Sender address
    To                []mail.Address       // Recipient addresses
    Bcc               []mail.Address       // Blind carbon copy addresses
    Cc                []mail.Address       // Carbon copy addresses
    Subject           string               // Email subject
    HTML              string               // HTML content
    Text              string               // Plain text content
    Headers           map[string]string    // Custom headers
    Attachments       map[string]io.Reader // File attachments
    InlineAttachments map[string]io.Reader // Inline/embedded attachments
}

Best practices

Always provide text alternative

message := &mailer.Message{
    // ... other fields
    HTML: htmlContent,
    Text: mailer.StripHTML(htmlContent),
}

Handle errors gracefully

if err := mailer.Send(message); err != nil {
    // Log error but don't fail the request
    app.Logger().Error("Failed to send email", "error", err)
    // Optionally queue for retry
}

Use templates for consistency

// Define reusable email templates
type EmailTemplates struct {
    welcome       *template.Template
    verification  *template.Template
    passwordReset *template.Template
}

// Load once at startup
templates := &EmailTemplates{
    welcome: template.Must(template.ParseFiles("templates/welcome.html")),
    // ...
}

Respect email preferences

user, _ := app.FindRecordById("users", userId)

if user.GetBool("email_notifications") {
    mailer.Send(message)
}