mail

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package mail integrates an outbound e-mail provider as a server-registered Atlas connector: a BPMN mail connector task sends a model-authored message through a configured provider via the job path (ADR-0079), mirroring how the clio package delegates an append to a registry-managed endpoint (ADR-0036). The integration inherits the job protocol's durability and non-blocking properties (ADR-0007):

  • A connector task creates a job carrying the reserved compiler.MailJobType. The processor never performs the outbound send itself, so it stays allocation-free (invariant I1) and free of any SMTP dependency.
  • The in-process Handler — a job worker — pulls those jobs, sends the message off the processor goroutine and after fsync (invariant I2, never inside applyToState / I4), and completes the job, which drives the token onward.
  • The provider host and credentials live in a server-side Registry keyed by connector name, so a model refers to a provider by name only and never carries a host or a secret (ADR-0036/0041). Only the message (recipients, subject, body) is authored in the model, like a REST task's endpoint (ADR-0067).

The first provider is SMTP (SMTPClient), which reaches Google, Microsoft 365, and any standards-compliant server via its submission endpoint; native Gmail / Microsoft Graph API providers are additive behind the same Client seam (ADR-0079).

Delivery is at-least-once (a crash between "the provider accepted the message" and "job completed" replays the send); every message carries the job key as its RFC 5322 Message-ID so a provider or downstream de-duplicator can recognize a replayed send rather than delivering it twice.

Index

Constants

View Source
const (
	ProviderSMTP      = "smtp"
	ProviderGmail     = "gmail"
	ProviderMicrosoft = "microsoft"
)

Provider identifiers for a managed mail connector. SMTP (the default) reaches any submission server; Gmail and Microsoft are the native provider APIs (ADR-0079/0081).

Variables

This section is empty.

Functions

func Handler

func Handler(store *state.Store, lookup ProcessLookup, reg *Registry) job.Handler

Handler builds a job handler that performs an outbound mail connector task. Register it with a job.Runner for the reserved compiler.MailJobTypeIndex; the runner then pulls activatable mail jobs, and for each the handler resolves the task's connector/recipients/subject/body from the compiled process — evaluating any FEEL field over the instance's variables (the fx toggle, ADR-0067) — resolves the named connector's provider client from reg, and sends the message keyed by the job key so an at-least-once retry de-duplicates (ADR-0079). Returning an error leaves the job pending (retry, then an incident, ADR-0061); the runner completes it only on success.

Types

type Client

type Client interface {
	Send(ctx context.Context, m Message) error
}

Client sends a Message through one configured mail provider. It is an interface so the worker is testable without a live server and so a connector name binds to exactly one provider (SMTP today; a native Gmail / Graph provider is additive).

func NewProviderClient

func NewProviderClient(cfg ProviderConfig) (Client, error)

NewProviderClient builds the mail client for a managed connector, dispatching on its provider. SMTP is the default; Gmail and Microsoft Graph parse the credential bundle and build an OAuth token source. A misconfigured connector returns an error so the caller can skip it (its tasks park) rather than sending wrongly. This is the single place a new provider is added.

type Connector

type Connector struct {
	Endpoint string
	Username string
	Password string
	From     string
}

Connector is the server-side configuration of one SMTP mail provider: the submission Endpoint ("host:port"), the auth Username and Password (the Password is the resolved secret — an app password or account password — held only at call time, never persisted, I6), and the default From address a task that authors no sender falls back to.

type GmailClient

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

GmailClient sends mail through the Gmail API (ADR-0093). It posts a base64url-encoded RFC 5322 message to /users/me/messages/send with a bearer token from its TokenSource; "me" resolves to the authenticated user (the impersonated subject under a service account, or the refresh token's user). It frames the message with the same MIME builder the SMTP client uses.

func NewGmailClient

func NewGmailClient(tokens TokenSource, baseURL, sender string) *GmailClient

NewGmailClient builds a Gmail mail client. baseURL defaults to the Gmail v1 API when empty; sender is the default From address a task without one falls back to.

func (*GmailClient) Send

func (c *GmailClient) Send(ctx context.Context, m Message) error

type GraphClient

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

GraphClient sends mail through the Microsoft Graph sendMail API (ADR-0093). It posts a structured message to /users/{mailbox}/sendMail with a bearer token from its TokenSource; the mailbox is the message's From or the connector's default sender. It reaches Microsoft 365 mailboxes with an app-only or refresh-token grant.

func NewGraphClient

func NewGraphClient(tokens TokenSource, baseURL, sender string) *GraphClient

NewGraphClient builds a Graph mail client. baseURL defaults to the Graph v1.0 API when empty; sender is the default mailbox to send as.

func (*GraphClient) Send

func (c *GraphClient) Send(ctx context.Context, m Message) error

type Message

type Message struct {
	From      string
	To        []string
	Cc        []string
	Bcc       []string
	Subject   string
	Body      string
	MessageID string
}

Message is one e-mail an outbound mail connector task sends. To is the required recipient list; Cc and Bcc are optional. From overrides the provider's default sender when set. MessageID is deterministic (the job key), so an at-least-once retry carries the same RFC 5322 Message-ID and can be de-duplicated rather than delivered twice.

type ProcessLookup

type ProcessLookup func(defKey uint64) *compiler.CompiledProcess

ProcessLookup resolves a process-definition key to its compiled process. The worker uses it to find the connector name and message fields a mail job belongs to, so one handler serves every deployed process.

type ProviderConfig

type ProviderConfig struct {
	Provider string
	Endpoint string
	Sender   string
	Secret   string
}

ProviderConfig is the per-connector data the server resolves before building a client: the provider, an optional endpoint override, the default sender, and the resolved Secret — an SMTP password, or (for a native provider) the OAuth credential JSON bundle held in the vault under the connector's credentialsRef (ADR-0093). The secret lives only here at build time, never in a model or an event (I6).

type Registry

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

Registry resolves a connector name to the Client for its mail provider. Connectors are registered at the server from managed configuration (endpoint plus credentials), so a model refers to a connector by name only (ADR-0036/0041). A Registry is read-only once populated and safe for concurrent use by workers.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty connector registry.

func (*Registry) Client

func (r *Registry) Client(name string) (Client, bool)

Client returns the client bound to name, or nil and false if none is registered.

func (*Registry) Register

func (r *Registry) Register(name string, c Client)

Register binds a connector name to its client. Registering the same name again replaces the earlier binding (last write wins), so reconfiguration is simple.

func (*Registry) Replace

func (r *Registry) Replace(clients map[string]Client)

Replace swaps the whole set of registered connectors at once, so a server can rebuild the registry from managed configuration after a change (ADR-0041). The caller must serialize Replace with the workers that read the registry — the Atlas server does both on its run-loop goroutine — so no lock is needed. A nil map clears the registry.

type SMTPClient

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

SMTPClient sends a Message over SMTP (the submission endpoint of any standards compliant provider, including Google and Microsoft 365). It authenticates with the connector's username/password when a username is configured, and frames the message as a UTF-8 text/plain e-mail.

func NewSMTPClient

func NewSMTPClient(conn Connector) *SMTPClient

NewSMTPClient builds an SMTP mail client for a configured connector, backed by net/smtp's SendMail.

func (*SMTPClient) Send

func (c *SMTPClient) Send(ctx context.Context, m Message) error

Send frames m as a UTF-8 text/plain e-mail and submits it to the connector's SMTP endpoint. The sender is the message's From, or the connector's default From when the task authored none; a message with no sender and no default is a configuration error. Recipients are the union of To, Cc and Bcc (the SMTP envelope); Bcc addresses are delivered but never written into a header. A missing recipient or a send failure returns an error so the job stays pending and is retried (at-least-once).

type TokenSource

type TokenSource interface {
	Token(ctx context.Context) (string, error)
}

TokenSource yields a valid OAuth2 bearer access token for a provider API, acquiring and refreshing it as it nears expiry. Implementations are safe for concurrent use by the mail worker.

Jump to

Keyboard shortcuts

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