email

package
v0.8.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package email holds what backd knows about the emails it asks a realm's delivery function to send: the kinds, the templates (loaded from <realm>/email/ into memory at startup and rendered per message), and the shape of the message handed to the function. backd never sends mail itself.

Index

Constants

View Source
const (
	VerifyEmail     = "verify-email"
	ResetPassword   = "reset-password"
	AccountExists   = "account-exists"
	PasswordChanged = "password-changed"
	Welcome         = "welcome"
	ChangeEmail     = "change-email"
	EmailChanged    = "email-changed"
	Invitation      = "invitation"
	// OrgInvitation invites to an organization ({{.Link}}, {{.Data.org_name}}, {{.Data.roles}},
	// {{.ExpiresAt}}).
	OrgInvitation = "org-invitation"
	// SecurityNotice tells the user a change to their sign-in security (a
	// second factor on or off, a password set or removed, a key created); the
	// template reads {{.Data.event}}. MFAFailed warns of wrong second-factor codes
	// after a right password.
	SecurityNotice = "security-notice"
	MFAFailed      = "mfa-failed"
	// ReauthCode carries the 6-digit code that proves a user with no password and no
	// second factor is there before a sensitive action ({{.Data.code}}, {{.ExpiresAt}}).
	ReauthCode = "reauth-code"
	// MFAReset tells a user that an administrator removed their second factor.
	MFAReset = "mfa-reset"
	// MagicLink carries the link and the 6-digit code that sign a user in
	// ({{.Link}}, {{.Data.code}}, {{.ExpiresAt}}); NoAccount tells the owner of an address with
	// no account that someone asked to sign in with it.
	MagicLink = "magic-link"
	NoAccount = "no-account"
	// TemplateFailed tells an organization's managers that a template they wrote couldn't be
	// used at send time ({{.Data.event}} is the kind, {{.Data.org_name}} the organization).
	TemplateFailed = "email-template-failed"
	// ConsentUpdated asks people to review the new version of a consent
	// ({{.Data.event}} is the consent's name); it is only sent by `backd consents notify`.
	ConsentUpdated = "consent-updated"
)

The kinds of email backd sends on its own.

View Source
const (
	PageVerifyEmail        = "verify-email"
	PageResetPassword      = "reset-password"
	PageConfirmEmailChange = "confirm-email-change"
	PageRevertEmailChange  = "revert-email-change"
	PageAcceptInvitation   = "accept-invitation"
	PageResult             = "result"
	PageInvalidToken       = "invalid-token"
	// PageOAuthError is shown when a provider sends the user back with a sign-in
	// attempt backd can't trace to an app. A realm may write its own
	// (<realm>/pages/oauth-error/<locale>.html); without one the built-in English
	// page is shown. It is not among PageKinds: a realm that only sends email
	// doesn't need it.
	PageOAuthError = "oauth-error"
	// PageMagicLink is what a magic link opens: a button that signs in (Kind "confirm"), or,
	// when the request has nowhere to send the browser, the instruction to type the code in the
	// app (Kind "type-code"). Optional like PageOAuthError: the built-in English page is
	// used when a realm has none.
	PageMagicLink = "magic-link"
)

The hosted pages. The first five are the forms a link opens; Result is shown after an action succeeds, InvalidToken for any link that doesn't work.

View Source
const DirName = "email"

DirName is the folder of a realm that holds its email templates.

View Source
const (

	// MaxTemplateBytes is the size of each of subject, text and HTML of an override.
	MaxTemplateBytes = 64 << 10
)

Limits of an owner template, so one can't be made to cost much.

View Source
const PagesDirName = "pages"

PagesDirName is the folder of a realm that holds its hosted pages: the pages the links in emails open (<realm>/pages/<kind>/<locale>.html).

Variables

View Source
var (
	// EscapeHTML escapes a value for the HTML part.
	EscapeHTML = html.EscapeString
	// EscapeText makes a value safe for the text part and the subject: no control characters
	// except a newline and a tab.
	EscapeText = func(s string) string {
		return strings.Map(func(r rune) rune {
			if r < ' ' && r != '\n' && r != '\t' || r == 0x7f {
				return -1
			}
			return r
		}, s)
	}
)

Escapers: HTML for the HTML part, and for text and subject a plain one that only drops control characters (text isn't HTML, and the subject is one line).

BuiltInKinds are system kinds a realm does not have to ship: when its email folder has none, the built-in English text is used for every listed locale, so a realm that existed before them keeps working. A folder of the kind replaces the built-in text.

View Source
var OptionalPageKinds = []string{PageOAuthError, PageMagicLink}

OptionalPageKinds are pages a realm may have in addition to PageKinds.

PageKinds are the pages every realm that sends email has, in every language it lists.

RequiredKinds must have a template in every realm that configures email.

SystemKinds are the kinds backd knows; `backd template realm` writes an English template for each.

Functions

func CompileOverride added in v0.8.3

func CompileOverride(kind string, o Override) (*CompiledOverride, []OverrideProblem)

CompileOverride parses an override and checks it: sizes, syntax (with where), and the placeholders its kind needs in the text and the HTML.

func DefaultFiles

func DefaultFiles(kind string) (map[string][]byte, error)

DefaultFiles returns the English template files of a system kind, by file name (en.subject.txt, en.txt, en.html).

func DefaultPage

func DefaultPage(kind string) ([]byte, error)

DefaultPage returns the English template of a hosted page.

func ParseMustache added in v0.8.3

func ParseMustache(src string) (*Mustache, *SyntaxError)

ParseMustache parses src, or says what is wrong and where.

func RenderBuiltin added in v0.8.2

func RenderBuiltin(kind string, d PageData) (string, error)

RenderBuiltin renders the built-in English page of a kind.

func RequiredPlaceholders added in v0.8.3

func RequiredPlaceholders(kind string) []string

RequiredPlaceholders are the values a template of kind must print, so the message still does its job: the link of a token kind, and for an invitation the organization it is to.

func SanitizeHTML added in v0.8.3

func SanitizeHTML(in string) (out string, removed []string)

SanitizeHTML makes the HTML of an owner template safe to put in an email (design/team-access.md §11): it keeps formatting tags, tables, links (https and mailto), images (https) and a short list of inline styles, and drops everything else: scripts, styles, forms, frames, event handlers, unknown tags (their text stays) and comments. The result is built from what was understood, not the input's own bytes, so what the parser misreads can't come through. removed lists, without repeats, what was dropped, for the person who wrote the template.

func ValidKind added in v0.8.3

func ValidKind(v string) bool

ValidKind reports whether v is a name a kind of email can have.

func ValidLocale

func ValidLocale(v string) bool

ValidLocale reports whether v looks like a BCP 47 language tag (es, es-MX).

Types

type CompiledOverride added in v0.8.3

type CompiledOverride struct {
	// contains filtered or unexported fields
}

CompiledOverride is a checked override, ready to render.

func (*CompiledOverride) Render added in v0.8.3

func (c *CompiledOverride) Render(d Data, orgName string) (msg Message, removed []string, err error)

Render renders the override. Every value is escaped (HTML for the HTML part), and the HTML that results passes the allowlist sanitizer; removed says what it dropped.

type Data

type Data struct {
	Realm     string
	User      User
	Link      string
	ExpiresAt time.Time
	Data      map[string]any
	// App is what the realm's email.data_hook returned (the company's name, colours, logo...):
	// nil when there is no hook, or it failed. Templates that use it guard with {{if .App}}.
	App map[string]any
}

Data is what a template can use: {{.Realm}}, {{.User.Email}}, {{.User.Locale}}, {{.Link}}, {{.ExpiresAt}}, {{.Data}} and {{.App}}. Templates never receive text written by whoever caused the email, so backd can't be used to send someone else's words.

func SampleData added in v0.8.3

func SampleData(kind string) Data

SampleData is what a template of kind is checked and previewed with.

func (Data) Context added in v0.8.3

func (d Data) Context(orgName string) map[string]any

Context is the data of an owner template, as maps: Realm, Link, ExpiresAt, User.Email, User.Locale, Data.*, App.* and Org.Name.

type Message

type Message struct {
	Subject string
	Text    string
	HTML    string
}

Message is a rendered email.

type Mustache added in v0.8.3

type Mustache struct {
	// contains filtered or unexported fields
}

Mustache is a parsed owner template.

func (*Mustache) Prints added in v0.8.3

func (m *Mustache) Prints(name string) bool

Prints reports whether the template prints name (not only tests it).

func (*Mustache) Render added in v0.8.3

func (m *Mustache) Render(data map[string]any, escape func(string) string) (string, error)

Render renders the template with data (maps, slices and scalars), escaping every value with escape. A value that is missing prints nothing, as in Mustache.

func (*Mustache) Variables added in v0.8.3

func (m *Mustache) Variables() []string

Variables lists the names the template prints or tests, each once, in order of appearance.

type Override added in v0.8.3

type Override struct {
	Subject string
	Text    string
	HTML    string
}

Override is a company's own template of one kind in one language (design/team-access.md §11): the three parts of a message, in the logic-less syntax of ParseMustache.

type OverrideProblem added in v0.8.3

type OverrideProblem struct {
	Part         string // subject, text or html
	Line, Column int    // 0 when the problem isn't at a place
	Message      string
}

OverrideProblem is one thing wrong with an override, for the person who wrote it.

func (OverrideProblem) String added in v0.8.3

func (p OverrideProblem) String() string

type PageData

type PageData struct {
	Realm  string
	Locale string
	Action string
	Token  string
	Error  string
	// ErrorCode names the problem in Error (password_mismatch, password_policy)
	// so a template can write it in its own words.
	ErrorCode string
	Kind      string
	// Email is the address the link is about: the new one for a change, the
	// previous one for an undo, the invited one for an invitation.
	Email string
	// Redirect and BackURL are checked against the realm's allowed_redirects
	// before they get here, so they may use an app's own scheme (acme://),
	// which html/template would otherwise refuse in a link.
	Redirect     htmltemplate.URL
	DelaySeconds int
	BackURL      htmltemplate.URL
	// App is what the realm's pages.data_hook returned for this page ({{.App.name}}): nil when
	// there is no hook, or it failed. Pages that use it guard with {{if .App}}.
	App map[string]any
}

PageData is what a page template can use: {{.Realm}}, {{.Locale}}, {{.Action}} (where its form posts), {{.Token}} (the hidden field), {{.Error}} and {{.ErrorCode}} (what to show on the form again), {{.Kind}} (which flow a result is for), {{.Email}} (the address the link is about), {{.Redirect}} and {{.DelaySeconds}} (where the result sends the user and when), {{.BackURL}} (a way back to the app on the error page).

type Pages

type Pages struct {
	// contains filtered or unexported fields
}

Pages are a realm's parsed hosted pages, held in memory.

func LoadPages

func LoadPages(dir string, locales []string) (*Pages, []error)

LoadPages reads <realm>/pages/ (dir): <kind>/<locale>.html for every kind of PageKinds and every one of locales. It parses and test-renders each page, and reports every problem together, naming the files.

func (*Pages) Has

func (p *Pages) Has(kind, locale string) bool

Has reports whether the page exists in locale.

func (*Pages) Render

func (p *Pages) Render(kind, locale string, d PageData) (string, error)

Render renders a page in locale.

type Purpose

type Purpose string

Purpose is what a token created for an email can be redeemed for.

func TokenPurpose

func TokenPurpose(kind string) Purpose

TokenPurpose returns the token an email of this kind carries, or "" when the kind has no link with a token.

func (Purpose) DefaultLifetime

func (p Purpose) DefaultLifetime() time.Duration

DefaultLifetime is how long a token of this purpose can be redeemed.

func (Purpose) LinkPath

func (p Purpose) LinkPath() string

LinkPath is the route of backd that a link of this purpose opens, under /v1/<realm>/_auth/.

type SyntaxError added in v0.8.3

type SyntaxError struct {
	Line, Column int
	Message      string
}

SyntaxError is a problem in an owner template, with where it is.

func (*SyntaxError) Error added in v0.8.3

func (e *SyntaxError) Error() string

type Templates

type Templates struct {
	// contains filtered or unexported fields
}

Templates are a realm's parsed templates, held in memory.

func Load

func Load(dir string, locales []string) (*Templates, []error)

Load reads <realm>/email/ (dir): one folder per kind, with <locale>.subject.txt, <locale>.txt and <locale>.html per locale. It parses and test-renders every template, requires the RequiredKinds, and requires every kind found to be complete in every one of locales. Problems are reported together, naming the files.

func (*Templates) Has

func (t *Templates) Has(kind, locale string) bool

Has reports whether kind has a template in locale.

func (*Templates) Kinds

func (t *Templates) Kinds() []string

Kinds lists the kinds that have templates, sorted.

func (*Templates) Render

func (t *Templates) Render(kind, locale string, d Data) (Message, error)

Render renders kind in locale.

type User

type User struct {
	Email  string
	Locale string
}

User is who a message is for.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL