When an entry is published, it is told:

  • to every open page, at once, on your PubSub;
  • to the subscribers, by email, with the :subscriptions feature;
  • to webhooks, Slack and Discord, with the :webhooks feature;
  • to your notifier, for anything else your app does: a push, an email to your own users, a post elsewhere. Your app knows its readers, its mailer and its voice.
defmodule MyApp.NewsNotifier do
  @behaviour Tayori.Notifier

  @impl true
  def published(entry) do
    MyApp.Push.news(entry)
  end
end
{Tayori, repo: MyApp.Repo, pubsub: MyApp.PubSub, notifier: MyApp.NewsNotifier}

When

Each publication of an entry is announced once:

  • when it is published now, a moment after;
  • when it is scheduled, at its time.

In a cluster, one node announces each entry: the announcement is claimed in the database. An entry unpublished and published again is announced again; an entry changed after it was published is not.

Readers see a scheduled entry at its time whatever happens to the announcement: what is announced is only what it sends.

The subscribers' email is the exception: with emails: [every: :each], the default, it goes with the announcement; with a period ({:weekly, day}, :monthly, {:days, n}), the entries wait for one email of the period (see Automation).

With Oban, or without

With Oban, give it to Tayori, and each email, each webhook and the call to your notifier is a job of Tayori.Worker, retried with Oban's backoff, through deploys and crashes:

{Tayori, repo: MyApp.Repo, pubsub: MyApp.PubSub, oban: Oban}

The jobs go to the :default queue, or the one you give (oban: [name: Oban, queue: :news]), which must be one your Oban runs. mix tayori.install sets oban: Oban when your app has Oban.

Without Oban, Tayori sends them from tasks in the node that announced the entry: a webhook is tried three times, an email once, and a failure is logged. A node stopped while it sends (a deploy, a crash) loses what it had not sent: no one gets an entry twice, but some may not get it. For subscribers or webhooks that matter, run Oban. Tayori.Jobs has the details.

With Oban, your notifier already runs in a job, so it can send directly and raise to be retried. Without it, put your sending in a job of your own when it should be retried.

The entry

The entry has its translations and its segments:

def deliver(entry_id) do
  entry = Tayori.Entries.get!(entry_id)
  config = Tayori.Config.get()

  for user <- MyApp.Accounts.users_for(entry.segments) do
    translation = Tayori.Entry.translation(entry, user.locale, config.locales)
    MyApp.Mailer.deliver(NewsEmail.new(user, entry, translation))
  end
end

The body is Kotoba content: translation.body.html for the email's HTML, translation.body.text for its text part, and translation.summary when the email gives a summary with a link.

The PubSub

Every open page hears of an entry on the app's PubSub (see Tayori), and so can your own processes:

Tayori.subscribe()

def handle_info({Tayori, :published, id}, state), do: ...