MishkaCmsCore.Mail (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Sending email, for every site through its own provider.

A consumer — a sign-in sender, a notification channel, an extension — builds a Swoosh.Email (email/2 fills the site's sender) and calls deliver/2 with the site it is for. It never names a provider: each scope picks one on the settings page's Email tab (mailer.provider), and providers are mishka_installer plugins (MishkaCmsCore.Mail.Provider), the core's own Resend included.

No borrowing

Each scope sends with what it set itself: a site with no provider, or one missing its key, sends nothing — not even through the installation's provider. nil as the site is the installation's own scope, which sends a master's email.

The path of one email

  1. "mail:deliver:before" — a plugin may change the email ({:reply, %{data | email: email}}) or stop it ({:reply, :halt, Map.put(data, :suppress, true)}). A test email passes here too.

  2. A MishkaCmsCore.Mail.Delivery row is written: :queued, or :not_configured with the reason, and "mail:not_configured" fires.
  3. A MishkaCmsCore.Mail.Workers.DeliverWorker job on the mail queue sends it — inline under env_test?. The job carries the email sealed (it may hold a sign-in link) and no key; inside the caller's transaction, a rolled-back registration takes its job with it.
  4. The provider answers: the row goes :sent and "mail:delivered" fires, or the attempt is retried while the provider says it may succeed, then the row goes :failed and "mail:failed" fires. Resend's idempotency key keeps a retry from sending twice.

The three notices are fired with return: false. Every job shows on the Activity page under its site, and every row on the Email tab.

The sender

email/2 fills each scope's own mailer.from, mailer.from_name and mailer.reply_to (resolve: :own). A site that set no address sends from noreply@ its own domain, never from another site's; one that set no name sends under its title.

Mail.email(site_id, to: "ada@example.com", subject: "Reset your password", html: "<p>…</p>", text: "…")
|> Mail.deliver(site_id: site_id, purpose: "password_reset")

History

A site's emails older than mailer.history_days are deleted when it next sends one — work done when something changed, never on a timer.

Summary

Functions

Whether site_id can send email now.

The provider a scope uses until it picks one: "none" — no email — unless the deployment says otherwise (config :mishka_cms_core, :mail_default_provider, the development mailbox in dev).

Hands email over for opts[:site_id] (nil for the installation). :purpose (required) says what the email is for — "password_reset", "magic_link", "confirmation", or anything an extension sends; :idempotency_key names this one email so a retry is never sent twice.

Why a send failed, in words an admin can act on: the provider's own message where it gave one, cut short. Never a key: providers' error bodies do not carry one, and nothing else is shown.

A new email from site_id (nil for the installation), with fields :to, :subject, :html and :text, and the scope's own sender. A missing :text is left out, and the provider writes one from the HTML.

The address site_id sends from: its own, or noreply@ its domain — the master site's for Global.

The name site_id sends under.

page(page, request) deprecated

The provider site_id sends with, ready to send: {:ok, module}, or {:error, reason} — :not_configured (none chosen), :unknown_provider (chosen, but its plugin is gone), :cannot_run (chosen, while the providers' plugins cannot run) or :not_ready (chosen, but missing what it needs, such as its key).

Deletes site_id's emails older than mailer.history_days — every site's for nil, the installation's own sweep. Run when a site sends, by the job that sent it.

A purpose in words, for the history table.

Where replies to site_id's email go, or nil for the from address.

Sends a test email to to for site_id now, through its provider, and waits for the answer: {:ok, message_id} or {:error, why} in words for the settings page. Recorded like any other.

The settings the Email tab shows: which provider a scope uses, then every offered provider's own, each drawn only while its provider is the one chosen (MishkaCmsCore.Settings.shown_when/3). Part of MishkaCmsCore.Settings' core definitions.

A delivery status in words.

Functions

aggregate(query, aggregate_or_aggregates, opts \\ [])

This function is deprecated. Use `Ash.aggregate/3` instead.

aggregate!(query, aggregate_or_aggregates, opts \\ [])

This function is deprecated. Use `Ash.aggregate!/3` instead.

avg(query, field, opts \\ [])

This function is deprecated. Use `Ash.avg/3` instead.

avg!(query, field, opts \\ [])

This function is deprecated. Use `Ash.avg!/3` instead.

bulk_create(inputs, resource, action, opts \\ [])

This function is deprecated. Use `Ash.bulk_create/4` instead.

bulk_create!(inputs, resource, action, opts \\ [])

This function is deprecated. Use `Ash.bulk_create!/4` instead.

bulk_destroy(stream_or_query, action, input, opts \\ [])

This function is deprecated. Use `Ash.bulk_destroy/4` instead.

bulk_destroy!(stream_or_query, action, input, opts \\ [])

This function is deprecated. Use `Ash.bulk_destroy!/4` instead.

bulk_update(stream_or_query, action, input, opts \\ [])

This function is deprecated. Use `Ash.bulk_update/4` instead.

bulk_update!(stream_or_query, action, input, opts \\ [])

This function is deprecated. Use `Ash.bulk_update!/4` instead.

calculate(resource, calculation, opts \\ [])

This function is deprecated. Use `Ash.calculate/3` instead.

calculate!(resource, calculation, opts \\ [])

This function is deprecated. Use `Ash.calculate!/3` instead.

can(action_or_query_or_changeset, actor, opts \\ [])

This function is deprecated. Use `Ash.can/3` instead.
@spec can(
  action_or_query_or_changeset ::
    Ash.Query.t()
    | Ash.Changeset.t()
    | {Ash.Resource.t(), atom() | Ash.Resource.Actions.action()},
  actor :: term(),
  opts :: Keyword.t()
) ::
  {:ok, boolean() | :maybe}
  | {:ok, true, Ash.Changeset.t() | Ash.Query.t()}
  | {:ok, true, Ash.Changeset.t(), Ash.Query.t()}
  | {:ok, false, Exception.t()}
  | {:error, term()}

can?(query_or_changeset_or_action, actor, opts \\ [])

This function is deprecated. Use `Ash.can?/3` instead.
@spec can?(
  query_or_changeset_or_action ::
    Ash.Query.t()
    | Ash.Changeset.t()
    | {Ash.Resource.t(), atom() | Ash.Resource.Actions.action()},
  actor :: term(),
  opts :: Keyword.t()
) :: boolean() | no_return()

configured?(site_id)

@spec configured?(String.t() | nil) :: boolean()

Whether site_id can send email now.

count(query, opts \\ [])

This function is deprecated. Use `Ash.count/2` instead.

count!(query, opts \\ [])

This function is deprecated. Use `Ash.count!/2` instead.

create(changeset, opts \\ [])

This function is deprecated. Use `Ash.create/2` instead.

create!(changeset, opts \\ [])

This function is deprecated. Use `Ash.create!/2` instead.

default_provider()

@spec default_provider() :: String.t()

The provider a scope uses until it picks one: "none" — no email — unless the deployment says otherwise (config :mishka_cms_core, :mail_default_provider, the development mailbox in dev).

default_short_name()

deliver(email, opts)

@spec deliver(Swoosh.Email.t(), keyword()) ::
  {:ok, MishkaCmsCore.Mail.Delivery.t() | :suppressed} | {:error, term()}

Hands email over for opts[:site_id] (nil for the installation). :purpose (required) says what the email is for — "password_reset", "magic_link", "confirmation", or anything an extension sends; :idempotency_key names this one email so a retry is never sent twice.

Returns {:ok, delivery} once it is queued (or sent, under env_test?), {:ok, :suppressed} when a plugin stopped it, {:error, :not_configured} when the scope has no ready provider, or {:error, reason}. It does not raise: an email is never worth failing the action that sent it.

describe(error)

@spec describe(term()) :: String.t()

Why a send failed, in words an admin can act on: the provider's own message where it gave one, cut short. Never a key: providers' error bodies do not carry one, and nothing else is shown.

destroy(record, opts \\ [])

This function is deprecated. Use `Ash.destroy/2` instead.

destroy!(record, opts \\ [])

This function is deprecated. Use `Ash.destroy!/2` instead.

email(site_id, fields)

@spec email(String.t() | nil, keyword()) :: Swoosh.Email.t()

A new email from site_id (nil for the installation), with fields :to, :subject, :html and :text, and the scope's own sender. A missing :text is left out, and the provider writes one from the HTML.

exists(query, opts \\ [])

This function is deprecated. Use `Ash.exists/2` instead.

exists?(query, opts \\ [])

This function is deprecated. Use `Ash.exists?/2` instead.

first(query, field, opts \\ [])

This function is deprecated. Use `Ash.first/3` instead.

first!(query, field, opts \\ [])

This function is deprecated. Use `Ash.first!/3` instead.

from_address(site_id)

@spec from_address(String.t() | nil) :: String.t()

The address site_id sends from: its own, or noreply@ its domain — the master site's for Global.

from_name(site_id)

@spec from_name(String.t() | nil) :: String.t()

The name site_id sends under.

get(resource, id_or_filter, opts \\ [])

This function is deprecated. Use `Ash.get/3` instead.

get!(resource, id_or_filter, opts \\ [])

This function is deprecated. Use `Ash.get!/3` instead.

list(query, field, opts \\ [])

This function is deprecated. Use `Ash.list/3` instead.

list!(query, field, opts \\ [])

This function is deprecated. Use `Ash.list!/3` instead.

load(data, query, opts \\ [])

This function is deprecated. Use `Ash.load/3` instead.

load!(data, query, opts \\ [])

This function is deprecated. Use `Ash.load!/3` instead.

max(query, field, opts \\ [])

This function is deprecated. Use `Ash.max/3` instead.

max!(query, field, opts \\ [])

This function is deprecated. Use `Ash.max!/3` instead.

min(query, field, opts \\ [])

This function is deprecated. Use `Ash.min/3` instead.

min!(query, field, opts \\ [])

This function is deprecated. Use `Ash.min!/3` instead.

page(page, request)

This function is deprecated. Use `Ash.page/2` instead.

page!(page, request)

This function is deprecated. Use `Ash.page!/2` instead.

provider(site_id)

@spec provider(String.t() | nil) :: {:ok, module()} | {:error, atom()}

The provider site_id sends with, ready to send: {:ok, module}, or {:error, reason} — :not_configured (none chosen), :unknown_provider (chosen, but its plugin is gone), :cannot_run (chosen, while the providers' plugins cannot run) or :not_ready (chosen, but missing what it needs, such as its key).

prune(site_id)

@spec prune(String.t() | nil) :: :ok

Deletes site_id's emails older than mailer.history_days — every site's for nil, the installation's own sweep. Run when a site sends, by the job that sent it.

purpose_label(purpose)

@spec purpose_label(String.t()) :: String.t()

A purpose in words, for the history table.

read(query, opts \\ [])

This function is deprecated. Use `Ash.read/2` instead.

read!(query, opts \\ [])

This function is deprecated. Use `Ash.read!/2` instead.

read_one(query, opts \\ [])

This function is deprecated. Use `Ash.read_one/2` instead.

read_one!(query, opts \\ [])

This function is deprecated. Use `Ash.read_one!/2` instead.

reload(record, opts \\ [])

This function is deprecated. Use `Ash.reload/2` instead.

reload!(record, opts \\ [])

This function is deprecated. Use `Ash.reload!/2` instead.

reply_to(site_id)

@spec reply_to(String.t() | nil) :: String.t() | nil

Where replies to site_id's email go, or nil for the from address.

run_action(input, opts \\ [])

This function is deprecated. Use `Ash.run_action/2` instead.

run_action!(input, opts \\ [])

This function is deprecated. Use `Ash.run_action/2` instead.

send_test(site_id, to)

@spec send_test(String.t() | nil, String.t()) ::
  {:ok, String.t() | nil} | {:error, String.t()}

Sends a test email to to for site_id now, through its provider, and waits for the answer: {:ok, message_id} or {:error, why} in words for the settings page. Recorded like any other.

settings()

@spec settings() :: [MishkaCmsCore.Settings.definition()]

The settings the Email tab shows: which provider a scope uses, then every offered provider's own, each drawn only while its provider is the one chosen (MishkaCmsCore.Settings.shown_when/3). Part of MishkaCmsCore.Settings' core definitions.

status_label(arg1)

@spec status_label(atom()) :: String.t()

A delivery status in words.

stream!(query, opts \\ [])

This function is deprecated. Use `Ash.stream!/2` instead.

sum(query, field, opts \\ [])

This function is deprecated. Use `Ash.sum/3` instead.

sum!(query, field, opts \\ [])

This function is deprecated. Use `Ash.sum!/3` instead.

update(changeset, opts \\ [])

This function is deprecated. Use `Ash.update/2` instead.

update!(changeset, opts \\ [])

This function is deprecated. Use `Ash.update!/2` instead.