A kind of notification of the app: who it goes to, by which channels, and what it says in each. Chime has none of its own; the app writes one module for each kind it has.
defmodule MyApp.Notifications.NewComment do
use Chime.Notifier, category: :comments, group_window: {10, :minute}
deliver :feed
deliver :email, wait: {10, :minute}, unless: :read
deliver :telegram, if: :telegram?
@impl true
def recipients(%{comment: comment}),
do: MyApp.Stories.followers(comment.story_id) -- [comment.author]
@impl true
def data(%{comment: comment}) do
%{"story_id" => comment.story_id, "story" => comment.story.title, "text" => comment.text}
end
@impl true
def actor(%{comment: comment}),
do: %{"id" => "user:#{comment.author.id}", "name" => comment.author.name}
@impl true
def group(%{comment: comment}), do: "story:#{comment.story_id}"
@impl true
def feed(data, notification) do
%{
title: "#{Chime.actors(notification)} commented on #{data["story"]}",
body: data["text"],
url: "/stories/#{data["story_id"]}"
}
end
@impl true
def email(data, _recipient, notification) do
Swoosh.Email.new(
subject: "#{Chime.actors(notification)} commented on #{data["story"]}",
text_body: data["text"]
)
end
def telegram?(_data, recipient), do: recipient.data.telegram_chat != nil
endThen, where the comment is made:
Chime.notify(MyApp.Notifications.NewComment, %{comment: comment},
key: "comment:#{comment.id}")Options of use
:category- the category ofChime.Configit is of, which the preferences turn on and off (required).:kind- its name in the JSON API and the components ("new_comment"forMyApp.Notifications.NewComment).:required- it goes whatever the person's preferences, and its emails have no link to unsubscribe (false; a category ofrequired: truemakes all its notifiers so).:notify_actor- whether the person who did it (actor/1) is notified too, when they are among the recipients (false: nobody is told of what they did).:audience_key- withaudience/1: the field of the query's first binding its pages go by (:id).:group_window- withgroup/1: how long a notification stays open to the events of its group, from the first ({1, :hour}).
Channels
deliver/2 declares a channel, in the order they go: :feed, the
bell and its list in the app (decided when the notification comes, with
:if and :unless of the notifier's functions only); :email, through the app's mailer; or
one of the app's :channels (Chime.Channel). Options:
:wait- how long after the notification it goes:{10, :minute}(:second,:minute,:hour,:day) or a number of seconds. What is decided then (the conditions, the preferences) is decided at that time.:unless- it does not go if::read(the person read it in the feed meanwhile),:seen(they saw it in the feed), or the name of a function of the notifier,(data, recipient) -> boolean. A list is any of them.:if- it goes only if this function of the notifier,(data, recipient) -> boolean, says so; or{module, function}.:throttle- at most one by the channel for a group (or, with no group, for the notifier) in this time:{15, :minute}. One that comes sooner waits for the end of it.:debounce- with:wait: each event that joins the group while it waits moves its time to:waitafter that event, so a quick run of events (messages a minute apart) goes as one.:max_waitis the most it waits from the first (none).:quiet-falsesends it in the person's quiet hours too (:quiet_hoursofChime.Config). A required notification always goes.
What it says
The feed and the emails are made from data/1, kept on the event as
JSON when the notification comes, and rendered when they are shown: in
the person's language, as the app's words are then, and with the count
of a group ("Ana and 4 others"). Each channel calls its function of
the notifier: feed/2, email/3, and, for an app's channel, the
function of its name (telegram/3) when there is one.
See the Notifiers guide.
Summary
Callbacks
Takes an action of the feed's :actions (Chime.act/3): with its key,
the data and the notification (notification.recipient is who takes
it). :ok, or {:ok, url} for where the person goes next; an error
leaves the notification as it was.
Who did it: %{"id" => ..., "name" => ...}, or nil (nil).
Everyone it goes to, for many people: a query of the app's (Ecto.Query),
from the event's data, instead of recipients/1. Chime goes through it
in Oban jobs, 1000 people a page, by the key of the query's first
binding (:audience_key of use, :id), and makes each key a person's
id with recipient_id/1
What is kept of the params, as JSON (a map of strings, numbers, lists and maps), for the feed and the channels to render from. By default, the params themselves, as JSON.
The email: a Swoosh.Email with its subject and body. Chime gives it
its To: and, unless the notification is required, the link and the
headers to unsubscribe; its From: is the config's unless it has one.
What the feed shows: :title, and :body, :url, :icon, and
:actions, the buttons of the notification: [%{key: "accept", label: "Accept"}, %{key: "decline", label: "Decline"}], which act/3
takes. Once the person took one, notification.action is its key.
The group its notifications join: a string, such as "story:42", or
nil. A person's notification of the same group, unread and within the
:group_window, takes the new event instead of a new notification: its
count goes up, its actors take the new one, and it comes to the top of
the feed. By default, nil: no groups.
Whether a person is notified at all: the guard of the app's rules, such
as "may this person read the channel?", checked for each person when
the notification comes, with the params of Chime.notify/3 and the
Chime.Recipient (as the resolver gives them). A person it says no to
gets nothing, by any channel. By default, everyone.
Params to see the notifier with, in the admin's previews
(Chime.Router.Admin): a map, as Chime.notify/3 gets them, or a list
of {name, params} for more than one. They are given to data/1 and
actor/1, so they can be plain maps of what those read
The id of a person of the audience, from its key (the key as a string).
Who it goes to: the app's users (turned into recipients by
Chime.Resolver.recipient/1), Chime.Recipients, or their ids.
Chime.notify/3 with :to does not call it.
The workspace it happens in: the id of a team, an organisation, a
collective of the app's, as a string, or nil (nil). Its notifications
are of that workspace: a feed, counts and preferences can be of one
workspace (see the Workspaces guide). Chime.notify/3
with :workspace gives it instead.
Functions
Declares a channel, with its options (see the moduledoc).
Types
Callbacks
@callback act(action :: String.t(), data :: map(), Chime.Notification.t()) :: :ok | {:ok, String.t()} | {:error, term()}
Takes an action of the feed's :actions (Chime.act/3): with its key,
the data and the notification (notification.recipient is who takes
it). :ok, or {:ok, url} for where the person goes next; an error
leaves the notification as it was.
Who did it: %{"id" => ..., "name" => ...}, or nil (nil).
@callback audience(data :: map()) :: Ecto.Queryable.t()
Everyone it goes to, for many people: a query of the app's (Ecto.Query),
from the event's data, instead of recipients/1. Chime goes through it
in Oban jobs, 1000 people a page, by the key of the query's first
binding (:audience_key of use, :id), and makes each key a person's
id with recipient_id/1:
@impl true
def audience(_data), do: from(u in MyApp.User, where: not is_nil(u.confirmed_at))
@impl true
def recipient_id(id), do: "user:#{id}"The guard (notify?/2) is not asked of an audience: its people are
the query's.
What is kept of the params, as JSON (a map of strings, numbers, lists and maps), for the feed and the channels to render from. By default, the params themselves, as JSON.
@callback email(data :: map(), Chime.Recipient.t(), Chime.Notification.t()) :: struct()
The email: a Swoosh.Email with its subject and body. Chime gives it
its To: and, unless the notification is required, the link and the
headers to unsubscribe; its From: is the config's unless it has one.
@callback feed(data :: map(), Chime.Notification.t()) :: feed()
What the feed shows: :title, and :body, :url, :icon, and
:actions, the buttons of the notification: [%{key: "accept", label: "Accept"}, %{key: "decline", label: "Decline"}], which act/3
takes. Once the person took one, notification.action is its key.
The group its notifications join: a string, such as "story:42", or
nil. A person's notification of the same group, unread and within the
:group_window, takes the new event instead of a new notification: its
count goes up, its actors take the new one, and it comes to the top of
the feed. By default, nil: no groups.
@callback notify?(params(), Chime.Recipient.t()) :: boolean()
Whether a person is notified at all: the guard of the app's rules, such
as "may this person read the channel?", checked for each person when
the notification comes, with the params of Chime.notify/3 and the
Chime.Recipient (as the resolver gives them). A person it says no to
gets nothing, by any channel. By default, everyone.
Params to see the notifier with, in the admin's previews
(Chime.Router.Admin): a map, as Chime.notify/3 gets them, or a list
of {name, params} for more than one. They are given to data/1 and
actor/1, so they can be plain maps of what those read:
@impl true
def preview do
[
{"A comment", %{comment: %{story_id: 1, story: %{title: "Tokyo"}, text: "Lovely.", author: %{id: 7, name: "Ana"}}}},
{"A long one", %{comment: %{story_id: 1, story: %{title: "Tokyo"}, text: String.duplicate("Lovely. ", 40), author: %{id: 7, name: "Ana"}}}}
]
end
The id of a person of the audience, from its key (the key as a string).
Who it goes to: the app's users (turned into recipients by
Chime.Resolver.recipient/1), Chime.Recipients, or their ids.
Chime.notify/3 with :to does not call it.
The workspace it happens in: the id of a team, an organisation, a
collective of the app's, as a string, or nil (nil). Its notifications
are of that workspace: a feed, counts and preferences can be of one
workspace (see the Workspaces guide). Chime.notify/3
with :workspace gives it instead.