Skip to main content

Module notify

Module notify 

Source
Expand description

Operator notifications for ACME lifecycle events.

The shape mirrors filter/signer: a trait, an error type, and a from_config selector building the configured set at startup. Three backends exist today — email, webhook (any HTTP endpoint, with the URL, method, headers and body all configured, which is what makes a chat provider configuration rather than code), and custom (an external script, for a channel that is not an HTTP request at all).

§Fire-and-forget, always — and now durable

A notification can never affect the ACME response that triggered it. NotifyDispatcher::dispatch returns () and cannot be ?’d: it writes the delivery to the durable queue and returns. That is a deliberate API shape, not a convention callers must remember — there is no method on this type whose result a handler could propagate.

What changed is what happens after it returns. Delivery used to be a bare tokio::spawn, so a refused SMTP connection or a 503 from a webhook was logged once and the notification was gone, and a restart lost everything in flight — the operator never heard that a certificate had been issued, and nothing recorded that they hadn’t. Now each delivery is a notify_deliver job row: a transport failure is retried under jobs.max_attempts and the shared backoff, and a row outlives the process that queued it.

Two consequences worth not rediscovering:

  • One job per (occurrence × backend), never one per event. A retry must not re-send to a backend that already succeeded, or one flaky webhook produces a duplicate email on every attempt.
  • NotifyError carries whether it is worth retrying. A template that does not render and a 400 from a webhook will fail identically for ever, so they are permanent and refused on the first attempt; a connection refused, a timeout and a 503 have decided nothing, so they are retried. That is crate::jobs’s Retry/Failed split, applied at the source.

§Per-backend event filtering

Unlike FilterConfig::rules, where every filter must agree, notify backends are independent broadcast side-channels — an operator plausibly wants email only for issuance/revocation and a chat webhook for everything including failures. Each backend’s own events list (defaulting to all six kinds) decides what reaches it, and the list lives on its BackendSlot so the check happens once, in NotifyDispatcher::dispatch, rather than in each backend’s own delivery code. Filtering there rather than in a wrapper around send is what stops a job being queued for a delivery that would immediately do nothing.

§Per-profile, like every other subsystem

build_registry builds one NotifyDispatcher per resolved profile (not deduplicated by configuration identity like signer backends are — dispatchers are stateless side-channels, so two profiles with identical [notify] sections simply get two independent instances). The asynchronous relay signer backend, whose completion happens outside any HTTP handler, is handed the whole profile name -> dispatcher map so it can notify the right profile once an order settles — see signer::relay::flow::settle.

Re-exports§

pub use job::NOTIFY_JOB_KIND;
pub use job::NotifyJob;

Modules§

custom
The custom notify backend: shells out to an external script or webhook wrapper, on the same contract as crate::filter::custom and crate::signer::custom — env vars plus JSON on stdin, exit code decides outcome. This is what lets an operator wire up a channel this server has no built-in support for (Slack, PagerDuty, …) without a code change.
email
The email notify backend: SMTP via lettre.
job
One queued notification delivery, as a durable job.
webhook
The webhook notify backend: one HTTP request per event, with the URL, the method, the headers and the body all stated by the operator.

Structs§

AccountCreatedData
Payload of NotifyEvent::AccountCreated: a client registered a new account at this endpoint.
AccountDeactivatedData
Payload of NotifyEvent::AccountDeactivated: an account was deactivated, either by the client (§7.3.6) or by acme-proxy account deactivate.
BackendSlot
One configured backend, under the id a queued job addresses it by.
CertificateIssuedData
Payload of NotifyEvent::CertificateIssued: a certificate was signed.
CertificateRevokedData
Payload of NotifyEvent::CertificateRevoked: a certificate was withdrawn.
ChallengeFailedData
Payload of NotifyEvent::ChallengeFailed: a validation attempt did not succeed.
Notifiers
The profile name -> dispatcher map, as a handle that survives a configuration reload.
NotifyDispatcher
The configured notify backends for one profile.
NotifyError
Why a notify backend failed to deliver, and whether asking again could help.
ProfileMountedData
Context common to every event: the profile it happened on and the client address, when a request was in scope.

Enums§

NotifyEvent
One lifecycle event, carrying everything a template or custom script needs to describe it.

Traits§

NotifyBackend
A pluggable notification channel.

Functions§

build_registry
Builds one NotifyDispatcher per resolved profile, keyed by profile name — the map the relay signer backend needs to notify the right profile from its background completion task, where there is no AppState/Profile to reach through.
from_config
Builds the configured notify dispatcher. Called once per profile at startup (via build_registry), so it may fail fast.
notifiers_channel
One cell the reload path replaces, and every reader sees the new map at the next get.

Type Aliases§

DispatcherMap
One generation’s profile name -> dispatcher map.
NotifiersSender
The writing half of a Notifiers handle.