Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
MyApp.Emails.UserEmail, not inline in contextsSwoosh.Preview in development — preview emails in the browser# mix.exs
defp deps do
[
{:swoosh, "~> 1.14"},
{:finch, "~> 0.18"},
{:gen_smtp, "~> 1.0"}
]
end
# application.ex
def start(_type, _args) do
children = [
# ...
{Finch, name: MyApp.Finch}
]
# ...
endAfter adding deps and configuring the supervision tree, confirm everything works before writing email modules:
# In iex -S mix (dev environment with Local adapter)
MyApp.Mailer.deliver(Swoosh.Email.new(to: "test@example.com", from: "noreply@myapp.com", subject: "Test"))
# => {:ok, %{}} — mailer configured correctly
# => {:error, ...} — Finch missing from supervision tree or adapter misconfiguredPrefer Phoenix components for rich templates. Use plain html_body/text_body strings only for simple one-off emails.
# lib/my_app/emails/user_email.ex
defmodule MyApp.Emails.UserEmail do
import Swoosh.Email
import Phoenix.Component, only: [sigil_H: 2]
alias MyAppWeb.EmailComponents
def welcome(user) do
assigns = %{user: user}
html =
~H"""
<EmailComponents.layout>
<h1>Welcome, <%= @user.name %>!</h1>
<p>Thanks for signing up.</p>
<EmailComponents.button href={url(~p"/dashboard")}>Get Started</EmailComponents.button>
</EmailComponents.layout>
"""
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
new()
|> to({user.name, user.email})
|> from({"MyApp", "noreply@myapp.com"})
|> subject("Welcome to MyApp!")
|> html_body(html)
|> text_body("Welcome, #{user.name}! Thanks for signing up.")
end
# Same pattern applies for password_reset, confirmation emails, etc.
# Build the html with ~H and EmailComponents, then pipe through new/to/from/subject/html_body/text_body.
endDefine reusable components in lib/my_app_web/components/email_components.ex. See EMAIL_COMPONENTS.md for a full example. A minimal layout and button:
# lib/my_app_web/components/email_components.ex
defmodule MyAppWeb.EmailComponents do
use Phoenix.Component
def layout(assigns) do
~H"""
<html>
<body style="font-family: sans-serif; max-width: 600px; margin: auto;">
<%= render_slot(@inner_block) %>
</body>
</html>
"""
end
def button(assigns) do
~H"""
<a href={@href} style="background: #4F46E5; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none;">
<%= render_slot(@inner_block) %>
</a>
"""
end
end# lib/my_app/mailer.ex
defmodule MyApp.Mailer do
use Swoosh.Mailer, otp_app: :my_app
end# config/dev.exs
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Local
config :swoosh, serve: true
# config/test.exs
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Test
# config/runtime.exs (production)
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: System.get_env("SENDGRID_API_KEY")defmodule MyApp.Workers.SendWelcomeEmail do
use Oban.Worker, queue: :mailers, max_attempts: 3
@impl Oban.Worker
def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
user = Accounts.get_user!(user_id)
user
|> MyApp.Emails.UserEmail.welcome()
|> MyApp.Mailer.deliver()
{:ok, :sent}
end
end
# Enqueue from context
def register_user(attrs) do
with {:ok, user} <- create_user(attrs) do
%{user_id: user.id}
|> MyApp.Workers.SendWelcomeEmail.new()
|> Oban.insert()
{:ok, user}
end
enddef register_user(attrs) do
with {:ok, user} <- create_user(attrs) do
Task.start(fn ->
user |> UserEmail.welcome() |> Mailer.deliver()
end)
{:ok, user}
end
enddefmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
import Swoosh.TestAssertions
test "sends welcome email on registration" do
attrs = %{email: "test@example.com", password: "password123"}
assert {:ok, user} = Accounts.register_user(attrs)
assert_email_sent(fn email ->
assert email.to == [{user.name, user.email}]
assert email.subject =~ "Welcome"
end)
end
test "no email sent on failed registration" do
attrs = %{email: "", password: ""}
assert {:error, _changeset} = Accounts.register_user(attrs)
assert_no_email_sent()
end
end# config/dev.exs
config :swoosh, serve: true
# Access preview at http://localhost:4000/dev/mailbox