Proca.Service.EmailBackend behaviour (proca v3.4.1)

EmailBackend behaviour specifies what we want to expect from an email backend. We are using Swoosh for sending emails - it is very convenient because it has lots of adapters. However, we also need to be able to work with templates and Swoosh does not have this.

Recipients

Recipients of transaction emails are Supporters, Targets, Users (in case of notifications)

We use Swoosh.Email for passing email data.

We make use of Email fields:

  • assigns - to hold merge tag values
  • private[:template] - template
  • private[:custom_id] - custom_id
  1. We use local email templates with single-sending
  2. We support batch sending for Mailjet, SES bulk api is not good.

Templates

  1. We use a mustache templates stored in proca (email_templates table)
  2. Or a service-provider template system (for Mailjet)

Sender Domain/Adddress

  1. We assume a sender has email backend with full domain support of their org.from_email address

  2. We can check this on time of setting the backend - querying the domains via API (when available - similar to how we check the template)

  3. We can do from email "spoofing" to use mixed FROM and Reply-To. Eg. A sends via B backend, their from is hello@a.org, the from will be hello+a.org@b.org, with Reply-To hello@a.org.

  4. We can use this also for MTT, but this creates a security risk of spoofing the real users' emails if we don't put username after + (member+marcin@a.org) but just in fromt of the @ (marcin@a.org). This could result into some serious problems, if someone abuses this to impersonate root@a.org, postmaster@a.org, or a legitimate user. Those who will have a dedicated campaign domain might feel in the safe, but imagine someone using this to access DNS provider for the domain... Another solution - if member+marcin@ is not good enought, to use a fixed prefix/postfix like member_marcin@.

Link to this section Summary

Link to this section Callbacks

Specs

batch_size() :: number()
Link to this callback

deliver(list, %Proca.Org{})

Specs

deliver(
  [
    %Swoosh.Email{
      assigns: term(),
      attachments: term(),
      bcc: term(),
      cc: term(),
      from: term(),
      headers: term(),
      html_body: term(),
      private: term(),
      provider_options: term(),
      reply_to: term(),
      subject: term(),
      text_body: term(),
      to: term()
    }
  ],
  %Proca.Org{
    __meta__: term(),
    action_pages: term(),
    action_schema_version: term(),
    campaigns: term(),
    config: term(),
    contact_schema: term(),
    custom_action_confirm: term(),
    custom_action_deliver: term(),
    custom_event_deliver: term(),
    custom_supporter_confirm: term(),
    detail_backend: term(),
    detail_backend_id: term(),
    doi_thank_you: term(),
    email_backend: term(),
    email_backend_id: term(),
    email_from: term(),
    event_backend: term(),
    event_backend_id: term(),
    high_security: term(),
    id: term(),
    inserted_at: term(),
    name: term(),
    public_keys: term(),
    push_backend: term(),
    push_backend_id: term(),
    services: term(),
    staffers: term(),
    storage_backend: term(),
    storage_backend_id: term(),
    supporter_confirm: term(),
    supporter_confirm_template: term(),
    title: term(),
    updated_at: term()
  }
) :: any()
Link to this callback

handle_bounce(params)

Specs

handle_bounce(params :: any()) :: any()
Link to this callback

handle_event(params)

Specs

handle_event(params :: any()) :: any()
Link to this callback

list_templates(org)

Specs

list_templates(
  org :: %Proca.Org{
    __meta__: term(),
    action_pages: term(),
    action_schema_version: term(),
    campaigns: term(),
    config: term(),
    contact_schema: term(),
    custom_action_confirm: term(),
    custom_action_deliver: term(),
    custom_event_deliver: term(),
    custom_supporter_confirm: term(),
    detail_backend: term(),
    detail_backend_id: term(),
    doi_thank_you: term(),
    email_backend: term(),
    email_backend_id: term(),
    email_from: term(),
    event_backend: term(),
    event_backend_id: term(),
    high_security: term(),
    id: term(),
    inserted_at: term(),
    name: term(),
    public_keys: term(),
    push_backend: term(),
    push_backend_id: term(),
    services: term(),
    staffers: term(),
    storage_backend: term(),
    storage_backend_id: term(),
    supporter_confirm: term(),
    supporter_confirm_template: term(),
    title: term(),
    updated_at: term()
  }
) ::
  {:ok,
   [
     %Proca.Service.EmailTemplate{
       __meta__: term(),
       compiled: term(),
       html: term(),
       id: term(),
       locale: term(),
       name: term(),
       org: term(),
       org_id: term(),
       ref: term(),
       subject: term(),
       text: term()
     }
   ]}
  | {:error, reason :: String.t()}
Link to this callback

supports_templates?(org)

Specs

supports_templates?(
  org :: %Proca.Org{
    __meta__: term(),
    action_pages: term(),
    action_schema_version: term(),
    campaigns: term(),
    config: term(),
    contact_schema: term(),
    custom_action_confirm: term(),
    custom_action_deliver: term(),
    custom_event_deliver: term(),
    custom_supporter_confirm: term(),
    detail_backend: term(),
    detail_backend_id: term(),
    doi_thank_you: term(),
    email_backend: term(),
    email_backend_id: term(),
    email_from: term(),
    event_backend: term(),
    event_backend_id: term(),
    high_security: term(),
    id: term(),
    inserted_at: term(),
    name: term(),
    public_keys: term(),
    push_backend: term(),
    push_backend_id: term(),
    services: term(),
    staffers: term(),
    storage_backend: term(),
    storage_backend_id: term(),
    supporter_confirm: term(),
    supporter_confirm_template: term(),
    title: term(),
    updated_at: term()
  }
) :: true | false

Link to this section Functions

Link to this function

batch_size(org)

Link to this function

deliver(recipients, org, email_template \\ nil)

Specs

deliver(
  [
    %Swoosh.Email{
      assigns: term(),
      attachments: term(),
      bcc: term(),
      cc: term(),
      from: term(),
      headers: term(),
      html_body: term(),
      private: term(),
      provider_options: term(),
      reply_to: term(),
      subject: term(),
      text_body: term(),
      to: term()
    }
  ],
  %Proca.Org{
    __meta__: term(),
    action_pages: term(),
    action_schema_version: term(),
    campaigns: term(),
    config: term(),
    contact_schema: term(),
    custom_action_confirm: term(),
    custom_action_deliver: term(),
    custom_event_deliver: term(),
    custom_supporter_confirm: term(),
    detail_backend: term(),
    detail_backend_id: term(),
    doi_thank_you: term(),
    email_backend: term(),
    email_backend_id: term(),
    email_from: term(),
    event_backend: term(),
    event_backend_id: term(),
    high_security: term(),
    id: term(),
    inserted_at: term(),
    name: term(),
    public_keys: term(),
    push_backend: term(),
    push_backend_id: term(),
    services: term(),
    staffers: term(),
    storage_backend: term(),
    storage_backend_id: term(),
    supporter_confirm: term(),
    supporter_confirm_template: term(),
    title: term(),
    updated_at: term()
  },
  %Proca.Service.EmailTemplate{
    __meta__: term(),
    compiled: term(),
    html: term(),
    id: term(),
    locale: term(),
    name: term(),
    org: term(),
    org_id: term(),
    ref: term(),
    subject: term(),
    text: term()
  }
  | nil
) :: :ok | {:error, [:ok | {:error, String.t()}]}

Delivers list of Email-s using EmailTemplate. Uses Org's email service.

:ok - all went fine {:error, [....]} - there was some error - could be partial!

Surprise: you can get {:error, [:ok, :ok, :ok]} with there was some error but adapter decided to drop the email (fatal problem, retry will not help)

Link to this function

determine_sender(email, org)

Determine the From + Reply to of the email.

Support these cases:

  1. No from set, and current org sends - use the org.email_from
  2. No from set, and org sends via other org - set the orgs FROM, then pass to other clause to get a mixed format
  3. FROM set, we need to check the address - if email is different then sending one, replace FROM email with a mix.
Link to this function

format_custom_id(type, id)

Link to this function

list_templates(org)

Link to this function

make_email(arg, custom_id)

Link to this function

parse_custom_id(custom_id)

Link to this function

service_module(atom)

Link to this function

supports_templates?(org)