anagrius

anagrius

Sending email as part of an action

I am currently implementing sending an email after e.g. a Comment resource is created.
I am doing it as a Changeset.after_action is this the right place to do it or should I be looking elsewhere?

Marked As Solved

zachdaniel

zachdaniel

Creator of Ash

There are four primary ways you can go about it, and they all depend on your requirements.

After action hooks - (at least once)

These are transactional with the create. What this means is that you could send an email, and then the comment could fail to create.

i.e

change after_action(fn changeset, result, context -> 
  send_email("...")
  boom!() # now you've sent an email for a comment that was never created
end)

After transaction hooks - (at most once)

These happen after the transaction. That means that the transaction can be committed, and then an error can occur, and so no email goes out.

change after_action(fn 
  changeset, {:ok, result}, context -> 
    boom!() # now you've created a comment where no email goes out
    send_email("...")
   changeset, {:error, error}, context ->
    {:error, error}
end)

Notifiers - (at most once)

This has the same properties as after transaction hooks, but comes in a different form factor.

defmodule SendNewCommentEmail do
  def notify(notification) do
    boom!() # now you've created a comment where no email goes out
    send_email(...)
  end
end

create :create do
  notifiers [SendNewCommentEmail]
end

Oban

This can technically apply to any durable & transactionally schedulable background queue, but Oban is far and above the obvious choice here in the Elixir ecosystem. This is how I would do it. @sezaru is absolutely correct.

create :create do
  change after_action(fn changeset, result -> 
    insert oban job
  end)
end

AshOban

For emails, I also prefer to create a resource (and in this case then a database table) for each email I wish to send. Then, I’ll use AshOban to send the emails using a trigger. It looks like this:

defmodule MyEmail do
  use Ash.Resource, data_layer: AshPostgres.DataLayer, extensions: [AshOban]

  attributes do
    uuid_primary_key :id
    attribute :state, :atom, constraints: [one_of: [:pending, :sent]]
  end

  actions do
     defaults [:create]

     update :send do
        change set_attribute(:state, :sent)
        change after_transaction(fn changeset, {:ok, result}, context -> 
           send_email(...)
        end)
    end
  end

  oban do
    triggers do
      trigger :send do
        where expr(state == :pending)
      end
    end
  end
end

Then, instead of inserting an Oban job in an after action hook, you can insert the email resource. THen, you can do things like show pending and/or sent emails in your application state, etc. You can also cancel the sending of any given email by deleting the email row, or setting its state to :sent, etc. Lots of small benefits from this strategy that stack up.

Also Liked

zachdaniel

zachdaniel

Creator of Ash

I’ve expanded on this in a substack post :slight_smile:

sezaru

sezaru

Here is how I do it:

First, I create an after_action that inserts an Oban job to send the e-mail. And and then I let Oban handle the e-mail sending part in a worker.

The advantage here is that even if the e-mail service is down, the action will still work and then you can handle the oban job as you wish (by retrying, etc).

This also means that the action will not be bottleneck by an slow e-mail service just because they are blocking the action to finish in the after_action call (because it is inside a DB transaction).

Where Next?

Popular in Questions Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New

Other popular topics Top

New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54120 245
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement