Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ config :prison_rideshare, PrisonRideshare.Repo,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
ssl: true

config :prison_rideshare, PrisonRideshare.Mailer,
deliver_later_strategy: PrisonRideshare.MailerRateLimiter,
rate_limit_ms: String.to_integer(System.get_env("MAILER_RATE_LIMIT_MS") || "30000")

config :prison_rideshare, gas_price_endpoint: System.get_env("GAS_PRICE_ENDPOINT")
1 change: 1 addition & 0 deletions lib/prison_rideshare/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule PrisonRideshare.Application do
{Phoenix.PubSub, name: PrisonRideshare.PubSub},
# Start the Ecto repository
PrisonRideshare.Repo,
PrisonRideshare.MailerRateLimiter,
# Start the endpoint when the application starts
PrisonRideshareWeb.Endpoint,
# Start your own worker by calling: PrisonRideshare.Worker.start_link(arg1, arg2, arg3)
Expand Down
82 changes: 82 additions & 0 deletions lib/prison_rideshare/mailer_rate_limiter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
defmodule PrisonRideshare.MailerRateLimiter do
@behaviour Bamboo.DeliverLaterStrategy
use GenServer

require Logger

@default_interval_ms 30000

def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end

@impl Bamboo.DeliverLaterStrategy
def deliver_later(adapter, email, config) do
GenServer.cast(__MODULE__, {:enqueue, adapter, email, config})
end

@impl GenServer
def init(_opts) do
{:ok,
%{
queue: :queue.new(),
interval_ms: delivery_interval_ms(),
in_flight: false
}}
end

@impl GenServer
def handle_cast({:enqueue, adapter, email, config}, state) do
state = %{state | queue: :queue.in({adapter, email, config}, state.queue)}
{:noreply, maybe_schedule(state)}
end

@impl GenServer
def handle_info(:deliver_next, state) do
case :queue.out(state.queue) do
{{:value, {adapter, email, config}}, queue} ->
deliver(adapter, email, config)
state = %{state | queue: queue}

if :queue.is_empty(queue) do
{:noreply, %{state | in_flight: false}}
else
Process.send_after(self(), :deliver_next, state.interval_ms)
{:noreply, state}
end

{:empty, _queue} ->
{:noreply, %{state | in_flight: false}}
end
end

defp maybe_schedule(%{in_flight: true} = state), do: state

defp maybe_schedule(state) do
send(self(), :deliver_next)
%{state | in_flight: true}
end

defp deliver(adapter, email, config) do
try do
case adapter.deliver(email, config) do
{:error, error} ->
Logger.error("Email delivery failed: #{inspect(error)}")

_ ->
:ok
end
rescue
exception ->
Logger.error(Exception.format(:error, exception, __STACKTRACE__))
catch
kind, reason ->
Logger.error(Exception.format(kind, reason, __STACKTRACE__))
end
end

defp delivery_interval_ms do
config = Application.get_env(:prison_rideshare, PrisonRideshare.Mailer, [])
config[:rate_limit_ms] || @default_interval_ms
end
end
Loading