add HTML email templates for newsletter
All checks were successful
deploy / deploy (push) Successful in 1m2s

Multipart emails (HTML + plain text fallback) with a branded wrapper:
shop name header, content area with auto-linked URLs and paragraph
formatting, and unsubscribe footer. Applied to both confirmation and
campaign emails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-28 23:32:27 +00:00
parent 620f31dde3
commit 199f0b506f
2 changed files with 245 additions and 11 deletions

View File

@@ -0,0 +1,136 @@
defmodule Berrypod.Newsletter.NotifierTest do
use Berrypod.DataCase, async: true
import Swoosh.TestAssertions
import Berrypod.NewsletterFixtures
alias Berrypod.Newsletter.Notifier
describe "deliver_confirmation/2" do
test "sends multipart email with confirmation link" do
sub = confirmed_subscriber_fixture(email: "confirm@example.com")
token = "test-token-123"
assert {:ok, _} = Notifier.deliver_confirmation(sub, token)
assert_email_sent(fn email ->
assert email.to == [{"", "confirm@example.com"}]
assert email.subject == "Confirm your subscription"
# text fallback
assert email.text_body =~ "confirm@example.com" || email.text_body =~ token
assert email.text_body =~ "/newsletter/confirm/test-token-123"
# html version
assert email.html_body =~ "Confirm subscription"
assert email.html_body =~ "/newsletter/confirm/test-token-123"
assert email.html_body =~ "<!DOCTYPE html>"
end)
end
end
describe "deliver_campaign/2" do
test "sends multipart email with HTML wrapper" do
sub = confirmed_subscriber_fixture(email: "reader@example.com")
campaign = campaign_fixture(subject: "Big news", body: "Hello world!\n\nSecond paragraph.")
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
assert_email_sent(fn email ->
assert email.to == [{"", "reader@example.com"}]
assert email.subject == "Big news"
# text fallback has body and unsubscribe
assert email.text_body =~ "Hello world!"
assert email.text_body =~ "Unsubscribe:"
# html version wraps in template
assert email.html_body =~ "<!DOCTYPE html>"
assert email.html_body =~ "<p>Hello world!</p>"
assert email.html_body =~ "<p>Second paragraph.</p>"
assert email.html_body =~ "Unsubscribe"
end)
end
test "replaces {{unsubscribe_url}} in both text and html" do
sub = confirmed_subscriber_fixture(email: "unsub@example.com")
campaign =
campaign_fixture(body: "Click here to unsubscribe: {{unsubscribe_url}}")
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
assert_email_sent(fn email ->
refute email.text_body =~ "{{unsubscribe_url}}"
refute email.html_body =~ "{{unsubscribe_url}}"
assert email.text_body =~ "/unsubscribe/"
assert email.html_body =~ "/unsubscribe/"
end)
end
test "includes List-Unsubscribe headers" do
sub = confirmed_subscriber_fixture()
campaign = campaign_fixture()
assert {:ok, _} = Notifier.deliver_campaign(campaign, sub)
assert_email_sent(fn email ->
headers = Map.new(email.headers)
assert headers["List-Unsubscribe"] =~ "/unsubscribe/"
assert headers["List-Unsubscribe-Post"] == "List-Unsubscribe=One-Click"
end)
end
end
describe "text_to_html/1" do
test "wraps paragraphs split by blank lines" do
html = Notifier.text_to_html("First paragraph.\n\nSecond paragraph.")
assert html =~ "<p>First paragraph.</p>"
assert html =~ "<p>Second paragraph.</p>"
end
test "preserves single line breaks within paragraphs" do
html = Notifier.text_to_html("Line one.\nLine two.")
assert html =~ "Line one.<br>Line two."
end
test "escapes HTML in content" do
html = Notifier.text_to_html("Use <b>bold</b> & \"quotes\"")
assert html =~ "&lt;b&gt;bold&lt;/b&gt;"
assert html =~ "&amp;"
assert html =~ "&quot;quotes&quot;"
end
test "turns URLs into links" do
html = Notifier.text_to_html("Visit https://example.com/shop for more")
assert html =~ ~s(<a href="https://example.com/shop")
assert html =~ "https://example.com/shop</a>"
end
end
describe "wrap_html/2" do
test "includes shop name in header" do
html = Notifier.wrap_html("Test Shop", "<p>Hello</p>")
assert html =~ "Test Shop"
assert html =~ "<!DOCTYPE html>"
assert html =~ "<p>Hello</p>"
end
test "includes unsubscribe link when provided" do
html = Notifier.wrap_html("Shop", "<p>Hi</p>", "https://example.com/unsub")
assert html =~ "https://example.com/unsub"
assert html =~ "Unsubscribe"
end
test "omits footer when no unsubscribe url" do
html = Notifier.wrap_html("Shop", "<p>Hi</p>")
refute html =~ "Unsubscribe"
end
test "escapes shop name in HTML" do
html = Notifier.wrap_html("Bob's <Shop>", "<p>Hi</p>")
assert html =~ "&lt;Shop&gt;"
refute html =~ "<Shop>"
end
end
end