Notification delivery¶
Defining a notification (see Notifications) resolves who
and what and dispatches a single, channel-neutral event. A delivery
channel turns that event into a real message and decides how it is sent.
This page is that half: the event that is the seam, the shipped channels
(orchestra_mail, orchestra_easy_email), and how to add your own or reach
another medium.
The split is deliberate: audience resolution and the dispatch points live once
in the *_notification modules, so a channel only has to subscribe and send.
The event¶
One event carries every notification, whatever the point that raised it:
OrchestraNotificationEvent {
recipients // Recipient value objects, already resolved by the *_notification module
type // a template id a channel renders on (task_assigned, interaction_link, generic, ...)
params // structured render data (label, link, message, ...)
instance, token, context // context carries the instance variables and the node
dispositions // optional per-recipient To/Cc/Bcc hint, keyed by recipient key
}
The payload is semantic, never rendered mail: each channel (and an ECA
model) templates it its own way, in each recipient's language. The
orchestra.notification emitter stamps every event with the process context
(the instance variables and the current node), so a channel can compose the
message from process values without re-reading the engine.
flowchart LR
EV["OrchestraNotificationEvent<br/>type + params + recipients"] --> CH{subscribers}
CH --> MAIL["orchestra_mail<br/>(hook_mail template)"]
CH --> EE["orchestra_easy_email<br/>(Easy Email template)"]
CH --> ECA["ECA model / custom<br/>subscriber"]
CH --> OTHER["SMS / chat / push"]
MAIL --> R["per-recipient render,<br/>each in their own language"]
EE --> R
Orchestra Mail¶
orchestra_mail is a dumb channel: one subscriber to
OrchestraNotificationEvent turns it into email, with the notification type as
the mail key so hook_mail renders the matching template (task_assigned,
task_reminder, interaction_link, and a neutral default for any custom type).
A send failure is logged on the Orchestra channel and never breaks the run.
Delivery follows the recipients' disposition:
- No Cc/Bcc (all recipients are To, which is every task notification and a simple Notify node): one private mail per recipient, each rendered in their own language. No one sees anyone else's address.
- Any Cc or Bcc present: the notification is a shared message, so it goes as
one email with
To/Cc/Bccheaders, rendered once (in the first recipient's language). With only Bcc recipients and no visible To, the blind copies are sent individually instead, so nothing forces a shared header.
Easy Email¶
Orchestra Easy Email (orchestra_easy_email) is a shipped alternative
channel that renders Easy Email
templates instead of hook_mail. It resolves a template from the notification
type (orchestra_<type>, falling back to orchestra_generic), so a site that
manages its transactional mail as Easy Email entities can theme Orchestra's
messages the same way. Enable it in place of (or alongside) orchestra_mail;
see its README for the template ids it looks up.
Customizing and replacing¶
- Reword a type:
hook_mail_alter()rewrites the subject or body of anyorchestra_mailmessage by its key (the example gives itsrequest_processedtype its own wording this way). - Deliver it differently: the event is the seam. Enable a different channel
in place of
orchestra_mailand it takes over, since a channel only has to subscribe and send. Or subscribe toOrchestraNotificationEventyourself, in a custom module or an ECA model, conditioning on thetypeand mapping the event's params and variables into the message. - A new audience: a custom
Audienceplugin joins in by implementingrecipients()and honoring the sharednotifyflag; to also staff a task it implementsAssignmentInterface(candidates()andviewerTokens()). See Task assignment.
Other channels: SMS, chat, push¶
The event carries recipients as Recipient value objects, not resolved
addresses. A recipient may be backed by a Drupal account or be account-less (a
fixed external email/phone), and a channel asks it for the contact detail it
needs rather than reading a user field directly. So a new delivery channel is
just another subscriber to OrchestraNotificationEvent, with no change to the
event, the audience resolution, the Notify node or the dispatchers. Each
Recipient exposes:
contact($channel): the address for a channel (mail,sms, ...), or NULL when the recipient has none for it;langcode($fallback): the recipient's preferred language, for a per-recipient render;label(): a human name for logs and templates;account(): the backingAccountInterface, or NULL for an account-less recipient.
So a channel reads what it needs:
orchestra_mailreads$recipient->contact('mail');- an SMS or WhatsApp channel reads
$recipient->contact('sms'); - a Slack, Teams or Telegram channel reads its own contact key;
- a push channel reads a stored device token contact.
Each channel templates from the event's type, params and the process
variables in context, in each recipient's language (langcode()). The
to/cc/bcc disposition is the mail channel's interpretation; another
channel is free to ignore it (message everyone) or reinterpret it (treat bcc
as a silent copy). Because a recipient can be account-less, a channel must never
assume account() is non-NULL; ask contact() instead.
Requirements¶
orchestra(defines the event, the emitter and the audience resolver)- a dispatcher submodule for the points you want:
orchestra_inbox_notification(tasks),orchestra_interaction_notification(bearer links),orchestra_notification(the Notify node) - a channel:
orchestra_mail,orchestra_easy_email, or your own subscriber