EXAMPLES
Real-world email templates.
Define emails as regular function components. Copy, paste, and send.
1def welcome(assigns) do
2 ~H"""
3 <.email>
4 <.head>
5 <.font
6 font_family="Inter"
7 fallback_font_family="Helvetica, Arial, sans-serif"
8 web_font={inter_regular()}
9 />
10 <.font
11 font_family="Inter"
12 font_weight={600}
13 fallback_font_family="Helvetica, Arial, sans-serif"
14 web_font={inter_semibold()}
15 />
16 </.head>
17 <.preview>You're in — let's get you set up</.preview>
18 <.body class="bg-zinc-100">
19 <.container class="mx-auto max-w-xl overflow-hidden rounded-xl bg-white">
20 <.section
21 class="p-10 text-center"
22 style="background:linear-gradient(135deg,#4f46e5,#7c3aed)"
23 >
24 <.icon_badge bg="rgba(255,255,255,0.18)" size={64} icon_size={28}>
25 <path d="M1.5 8.67v8.58a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V8.67l-8.928 5.493a3 3 0 0 1-3.144 0L1.5 8.67Z" />
26 <path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" />
27 </.icon_badge>
28 </.section>
29 <.section class="p-8">
30 <.heading as="h1" class="text-2xl font-bold tracking-tight text-zinc-900">
31 Hello {@name} 👋
32 </.heading>
33 <.text class="text-base leading-relaxed text-zinc-600">
34 Thanks for signing up. We're excited to have you — click below to finish setting up your account.
35 </.text>
36 <.button
37 href={@url}
38 class="mt-6 rounded-lg bg-indigo-600 px-6 py-3 font-semibold text-white"
39 >
40 Get started
41 </.button>
42 <.text class="mt-4 text-xs text-zinc-400">
43 Or paste this link into your browser: {@url}
44 </.text>
45 <.hr class="my-6 border-zinc-100" />
46 <.text class="text-xs text-zinc-400">
47 You're receiving this email because you signed up for Acme.
48 </.text>
49 </.section>
50 </.container>
51 </.body>
52 </.email>
53 """
54end
55
56 attr :bg, :string, required: true
57 attr :size, :integer, default: 48
58 attr :icon_size, :integer, default: 22
59 slot :inner_block, required: true
60
61defp icon_badge(assigns) do
62 assigns = assign(assigns, :padding, div(assigns.size - assigns.icon_size, 2))
63
64 ~H"""
65 <svg
66 xmlns="http://www.w3.org/2000/svg"
67 viewBox="0 0 24 24"
68 width={@size}
69 height={@size}
70 fill="#ffffff"
71 style={"box-sizing:border-box;padding:#{@padding}px;border-radius:9999px;background-color:#{@bg}"}
72 >
73 {render_slot(@inner_block)}
74 </svg>
75 """
76end
77
78defp inter_regular do
79 %{
80 url:
81 "https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.16/files/inter-latin-400-normal.woff2",
82 format: "woff2"
83 }
84end
85
86defp inter_semibold do
87 %{
88 url:
89 "https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.16/files/inter-latin-600-normal.woff2",
90 format: "woff2"
91 }
92end
Greet a new user and point them to their first action.
render.ex
1# Render to HTML
2html = PhoenixEmail.render(
3 &MyApp.Emails.welcome/1,
4 %{name: "Ada", url: "https://example.com/start"}
5)
6
7# Render to Plain Text for multipart emails
8text = PhoenixEmail.render(
9 &MyApp.Emails.welcome/1,
10 %{name: "Ada", url: "https://example.com/start"},
11 plain_text: true
12)
Function components
Standard Elixir functions — no new concepts to learn.
HEEx syntax
Everything you know from LiveView works here.
HTML + plain text
One template, both formats for multipart emails.