What each person wants to hear about: a setting for each category and each channel.

Categories

The config names the categories, in the order the preferences show them:

categories: [
  comments: [label: "Comments", description: "On your stories"],
  followers: [label: "New followers", defaults: [email: :weekly]],
  mentions: [label: "Mentions", defaults: [email: :on, telegram: :off]],
  billing: [label: "Billing", required: true]
]
  • :label and :description - what the person reads.
  • :defaults - the setting of each channel for a person who has not changed it: :on, :off, and for email :daily or :weekly. A channel not named is :on.
  • :required - its notifications go whatever the settings, and their emails have no link to unsubscribe: receipts, security, the account. Its settings cannot be changed.

Every notifier has a category of the config. Without :categories, any category is taken, with every channel on, and the preferences show none.

Settings

Setting
"on"it goes
"off"it does not, and the log says when the person turned it off
"daily", "weekly"email only: it waits for the person's digest
Chime.preferences("user:42")
#=> [%{category: %{key: "comments", label: "Comments", ...}, settings: %{"feed" => "on", "email" => "daily"}}, ...]

Chime.put_preference("user:42", :comments, :email, :daily)
#=> :ok
Chime.put_preference("user:42", :billing, :email, :off)
#=> {:error, :required}

The feed turned off for a category keeps its notifications out of the feed and its counts; its other channels still go.

A setting is read when each delivery goes, so a person who turns email off now gets none of the emails that wait.

The forms

  • In LiveView: <Chime.Components.preferences id="notifications" chime={@chime} />, a table with a choice for each category and channel, kept as it changes.
  • In any frontend: GET and PUT /preferences of chime_api (see Any frontend).

Digests

A category's email set to "daily" or "weekly" does not go at its time: its delivery waits, as "digest", with the rest decided (the conditions of the notifier, the person's other settings). Every hour, a job finds the people whose digest is due, in their own time zone (the recipient's :time_zone, UTC without one): at the config's hour, every day for daily, on its day for weekly.

{Chime, ..., digest: [at: 9, weekly_on: :monday, email: MyApp.ChimeDigest]}

The time zones need a time zone database, set as Elixir's:

config :elixir, :time_zone_database, Tz.TimeZoneDatabase

The digest's email is Chime's (a list of the notifications as the feed shows them, with links whole from the config's :url), or your module's email(recipient, items, period); see Chime.Digest. It carries a link that turns off every email but the required.

Unsubscribing

Each email has a link to turn off its category's emails, and the headers of one-click unsubscribe; the digest's turns off every email. Mount the page at the config's :mail_url:

pipeline :mail do
  plug :accepts, ["html"]
end

scope "/" do
  pipe_through :mail
  chime_mail "/notifications/mail", app_name: "MyApp"
end

A GET asks first, with a button: mail clients and scanners open links, and must not unsubscribe anyone. A POST, the button's or a mail client's one-click, turns the emails off. The link is signed with the config's :secret_key_base, and does not expire.