courier

package module
v0.0.2 Latest Latest
Warning

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

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

README

backplane-courier

Message delivery for a Backplane installation: mail, push, and any provider a plugin adds, through the installation's own channels. A product never imports it: it raises a hook or publishes an event, and the operator wires that to a courier activity in the console, usually after templates.Render.

Courier keeps nothing. Every activity delivers at once and answers; a temporary failure goes back to the caller, whose retry policy (backplane's, per step and per for-each item) decides; a final one is not retried; a provider's Retry-After becomes activity.RetryAfter. Every delivery is a platform audit record (courier.delivered, courier.failed; recipients and tokens hashed, never a payload). Encryption, fan-out, muting and the like are the product's and the installation's choices: courier exposes each provider's full semantics.

Activities

Activity What
courier.SendMail A rendered mail (to, cc, bcc, reply_to, subject, text, html, headers, attachments, optional from, channel) through a mail channel. Answers {channel, message_id}.
courier.SendPush A push to one token, topic or condition: title, body, image, data, priority, ttl_seconds, collapse_key, and the raw FCM android, apns, webpush, fcm_options blocks merged over them, so every FCM field is reachable (mutable-content, content-available, apns-push-type, ...). validate_only checks with FCM without delivering. Answers {channel, name}.
courier.Subscribe, courier.Unsubscribe Device tokens (at most 1000) to or from an FCM topic; answers each token's outcome.
courier.Send<Kind> One per provider kind a plugin or a compiled-in provider adds (SendSms), with that provider's message schema plus an optional channel.

A message names its channel, else the kind's default (defaults), else the kind's only channel.

Channels

channels:
  mail:
    smtp: {host: smtp.example.com, port: 587, tls: starttls, username: shop, password: ${SMTP_PASSWORD}, from: "Shop <no-reply@shop.example>"}
    pool: 8
    rate_per_minute: 600
    timeout: 30s
  push:
    fcm: {credentials: ${FCM_KEY}}      # service account key JSON; project_id from it unless set
  dev: {log: {kind: mail}}               # deliveries of a kind go to the log
  sms:
    provider: {name: smsc, settings: {sender: Shop}, secrets: {token: ${SMSC_TOKEN}}}
plugins:
  smsc:
    address: unix:///run/courier/smsc.sock   # or host:port
    tls: {enabled: false}                    # TLS or mTLS to a remote plugin
    startup_timeout: 30s
defaults: {mail: mail}                 # needed only when a kind has several channels
dry_run: false                         # live: record deliveries, send nothing

Providers courier does not know

A plugin is a gRPC service implementing courier.plugin.v1.ProviderService (proto/courier/plugin/v1): Describe (its kind, the schemas of a channel's settings, of a message and of a result), Check and Send. In Go, implement provider.Provider and call plugin.Serve(address, p) (examples/smsplugin); in any other language, implement the proto. Where and how the plugin runs (a sidecar, another container, a process the image starts) is the deployment's concern: courier dials its address. Courier waits for it at startup up to startup_timeout, probes its standard gRPC health as part of its own readiness, keeps the connection alive and lets gRPC reconnect. The contract evolves as protobuf does; an incompatible change is a new package.

A compiled-in provider is a provider.Provider built into a custom binary:

func main() { courier.Main(courier.WithProvider("smsc", smsc.New())) }

Either way the provider's channels use {provider: {name: ...}}, courier declares courier.Send<Kind> with the provider's schema, and a provider.Error says whether a failure is final or when to retry.

Development

make configure          # linter and proto generators into ./bin
make configure-web      # the console module's packages (GitHub Packages)
make gen                # Go code of the protos and the module's TypeScript client
make test
make web                # build the console module (embedded by make build)
go run ./examples/smsplugin &   # the example plugin of deployments/dev.yaml
make run                # against the local stack with deployments/dev.yaml

Tests run fake SMTP and FCM endpoints in process (internal/smtp/smtptest, internal/fcm/fcmtest) and a plugin over a unix socket.

Documentation

Overview

Package courier is the courier service as a library: Main runs it, with the providers a custom build compiles in.

func main() { courier.Main(courier.WithProvider("smsc", smsc.New())) }

The stock binary (cmd/courier) is Main with nothing added. A provider in another language runs as a plugin (package plugin) the configuration names by address.

Index

Constants

This section is empty.

Variables

View Source
var ErrProviderName = errors.New("courier: provider name used twice")

ErrProviderName: a provider name is used twice.

Functions

func Main

func Main(opts ...Option)

Main runs courier until a signal stops it, exiting the process on error.

func Run

func Run(ctx context.Context, opts ...Option) error

Run runs courier until ctx ends or a signal stops it.

Types

type Config

type Config struct {
	config.Backplane `json:"backplane"`

	// Channels by name; each has exactly one provider.
	Channels channels.Config `json:"channels"`
	// Plugins by name: providers courier dials.
	Plugins map[string]channels.Plugin `json:"plugins,omitempty"`
	// Defaults: the channel of a kind (mail, push, a provider's) when a
	// message names none; a kind with one channel needs no default.
	Defaults map[string]string `json:"defaults,omitempty"`
	// DryRun records deliveries without sending them; live.
	DryRun config.Live[bool] `json:"dry_run" schemapb:"default=false;description=Record deliveries, send nothing"`
	// MaxMessageBytes bounds a mail's bodies and attachments.
	MaxMessageBytes int64 `json:"max_message_bytes" schemapb:"default=26214400;gte=4096"`
	// MaxRecipients bounds a mail's to + cc + bcc.
	MaxRecipients int64 `json:"max_recipients" schemapb:"default=100;gte=1;lte=1000"`
}

Config is the whole configuration: file (BACKPLANE_CONFIG_FILE) < env COURIER_* (BACKPLANE_* for the SDK block) < Consul KV for Live fields.

func (*Config) Validate

func (c *Config) Validate() error

Validate what the schema cannot.

type Option

type Option func(*options)

Option configures Main.

func WithProvider

func WithProvider(name string, p provider.Provider) Option

WithProvider compiles p in under name: channels use it as {provider: {name: <name>}}.

func WithTokenSource

func WithTokenSource(ts oauth2.TokenSource) Option

WithTokenSource authorizes every FCM channel with ts instead of its credentials (tests, a sidecar issuing tokens).

type State

type State struct {
	Channels *channels.Channels
	Courier  *core.Courier
	Admin    *admin.Admin
	// contains filtered or unexported fields
}

State is the service's tree.

Directories

Path Synopsis
cmd
courier command
courier is a Backplane tool service: it delivers rendered messages — mail, push, and whatever a provider plugin adds — through the channels of an installation.
courier is a Backplane tool service: it delivers rendered messages — mail, push, and whatever a provider plugin adds — through the channels of an installation.
examples
smsplugin command
smsplugin is an example courier plugin: an SMS "gateway" that logs what it would send.
smsplugin is an example courier plugin: an SMS "gateway" that logs what it would send.
internal
admin
Package admin is courier's internal API: the console module's channel list and check, served on the platform port behind the internal secret.
Package admin is courier's internal API: the console module's channel list and check, served on the platform port behind the internal secret.
channels
Package channels holds the installation's channels: their configuration (exactly one provider each), the runtime of each (pool, rate, timeout) and which channel a kind uses by default.
Package channels holds the installation's channels: their configuration (exactly one provider each), the runtime of each (pool, rate, timeout) and which channel a kind uses by default.
courier
Package courier is the service's work: an activity per kind of delivery — SendMail, SendPush (with Subscribe and Unsubscribe for FCM topics) and Send<Kind> for every extension provider — each delivering at once through the channel the message names or the kind's default.
Package courier is the service's work: an activity per kind of delivery — SendMail, SendPush (with Subscribe and Unsubscribe for FCM topics) and Send<Kind> for every extension provider — each delivering at once through the channel the message names or the kind's default.
fcm
Package fcm is the built-in push provider: FCM HTTP v1 per channel (a Firebase project) with OAuth2 from a service account, and topic subscriptions through the Instance ID API.
Package fcm is the built-in push provider: FCM HTTP v1 per channel (a Firebase project) with OAuth2 from a service account, and topic subscriptions through the Instance ID API.
fcm/fcmtest
Package fcmtest is a fake FCM endpoint for tests (HTTP v1 sends and Instance ID subscriptions): it records what it receives and answers by rules on the target.
Package fcmtest is a fake FCM endpoint for tests (HTTP v1 sends and Instance ID subscriptions): it records what it receives and answers by rules on the target.
mail
Package mail is the service's domain: the message a caller hands to courier.SendMail, its checks and limits, address hashing and the MIME it becomes.
Package mail is the service's domain: the message a caller hands to courier.SendMail, its checks and limits, address hashing and the MIME it becomes.
push
Package push is the push domain: the message a caller hands to courier.SendPush, its checks, token hashing and the FCM HTTP v1 message it becomes.
Package push is the push domain: the message a caller hands to courier.SendPush, its checks, token hashing and the FCM HTTP v1 message it becomes.
smtp
Package smtp is the built-in mail provider: one SMTP relay per channel, a connection per delivery, 5xx replies final.
Package smtp is the built-in mail provider: one SMTP relay per channel, a connection per delivery, 5xx replies final.
smtp/smtptest
Package smtptest is an SMTP server for tests: it keeps what it receives and refuses the recipients it is told to.
Package smtptest is an SMTP server for tests: it keeps what it receives and refuses the recipients it is told to.
ui
Package ui carries the console module: web/dist/plugin copied here by `make web` and embedded into the binary.
Package ui carries the console module: web/dist/plugin copied here by `make web` and embedded into the binary.
Package plugin connects courier and a provider running as its own service over courier.plugin.v1.ProviderService.
Package plugin connects courier and a provider running as its own service over courier.plugin.v1.ProviderService.
proto
Package provider is what a courier provider implements: one way to deliver messages courier does not know.
Package provider is what a courier provider implements: one way to deliver messages courier does not know.

Jump to

Keyboard shortcuts

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