defmodule PlausibleWeb.EmailTest do use Plausible.DataCase, async: true import Plausible.Factory alias PlausibleWeb.Email describe "base_email layout" do test "greets user by first name if user in template assigns" do email = Email.base_email() |> Email.render("welcome_email.html", %{ user: build(:user, name: "John Doe"), code: "123" }) assert email.html_body =~ "Hey John," assert email.text_body =~ "Hey John," end test "greets impersonally when user not in template assigns" do email = Email.base_email() |> Email.render("welcome_email.html") assert email.html_body =~ "Hey," assert email.text_body =~ "Hey," end test "renders plausible link" do email = Email.base_email() |> Email.render("welcome_email.html") assert email.html_body =~ plausible_link() assert email.text_body =~ plausible_url() end @tag :ee_only test "renders unsubscribe placeholder" do email = Email.base_email() |> Email.render("welcome_email.html") assert email.html_body =~ "{{{ pm:unsubscribe }}}" end test "can be disabled with a nil layout" do email = Email.base_email(%{layout: nil}) |> Email.render("welcome_email.html", %{ user: build(:user, name: "John Doe") }) refute email.html_body =~ "Hey John," refute email.text_body =~ "Hey John," end end describe "priority email layout" do @tag :ee_only test "uses the `priority` message stream in Postmark in EE" do email = Email.priority_email() |> Email.render("activation_email.html", %{ user: build(:user, name: "John Doe"), code: "123" }) assert %{"MessageStream" => "priority"} = email.private[:message_params] end @tag :ce_build_only test "doesn't use the `priority` message stream in Postmark in CE" do email = Email.priority_email() |> Email.render("activation_email.html", %{ user: build(:user, name: "John Doe"), code: "123" }) refute email.private[:message_params]["MessageStream"] end test "greets user by first name if user in template assigns" do email = Email.priority_email() |> Email.render("activation_email.html", %{ user: build(:user, name: "John Doe"), code: "123" }) assert email.html_body =~ "Hey John," assert email.text_body =~ "Hey John," end test "greets impersonally when user not in template assigns" do email = Email.priority_email() |> Email.render("password_reset_email.html", %{ reset_link: "imaginary" }) assert email.html_body =~ "Hey," assert email.text_body =~ "Hey," end test "renders plausible link" do email = Email.priority_email() |> Email.render("password_reset_email.html", %{ reset_link: "imaginary" }) assert email.html_body =~ plausible_link() assert email.text_body =~ plausible_url() end test "does not render unsubscribe placeholder" do email = Email.priority_email() |> Email.render("password_reset_email.html", %{ reset_link: "imaginary" }) refute email.html_body =~ "{{{ pm:unsubscribe }}}" end test "can be disabled with a nil layout" do email = Email.priority_email(%{layout: nil}) |> Email.render("password_reset_email.html", %{ reset_link: "imaginary" }) refute email.html_body =~ "Hey John," refute email.html_body =~ plausible_link() refute email.text_body =~ "Hey John," refute email.text_body =~ plausible_url() end end describe "over_limit_email/3" do test "renders usage, suggested plan, and links to upgrade and account settings" do user = build(:user) team = build(:team, identifier: Ecto.UUID.generate()) penultimate_cycle = Date.range(~D[2023-03-01], ~D[2023-03-31]) last_cycle = Date.range(~D[2023-04-01], ~D[2023-04-30]) usage = %{ penultimate_cycle: %{date_range: penultimate_cycle, total: 12_300}, last_cycle: %{date_range: last_cycle, total: 32_100} } %{html_body: html_body, subject: subject} = PlausibleWeb.Email.over_limit_email(user, team, usage, "100k") assert subject == "[Action required] You have outgrown your Plausible subscription tier" assert html_body =~ PlausibleWeb.TextHelpers.format_date_range(last_cycle) assert html_body =~ "We recommend you upgrade to the 100k pageviews/month plan" assert html_body =~ "your account recorded 32,100 billable pageviews" assert html_body =~ "cycle before that (#{PlausibleWeb.TextHelpers.format_date_range(penultimate_cycle)}), your account used 12,300 billable pageviews" assert text_of_element( html_body, ~s|a[href$="/billing/choose-plan?__team=#{team.identifier}"]| ) == "Click here to upgrade your subscription" assert text_of_element( html_body, ~s|a[href$="/settings/billing/subscription?__team=#{team.identifier}"]| ) == "account settings" assert html_body =~ PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{team.identifier}" end test "asks enterprise level usage to contact us" do user = build(:user) team = build(:team, identifier: Ecto.UUID.generate()) penultimate_cycle = Date.range(~D[2023-03-01], ~D[2023-03-31]) last_cycle = Date.range(~D[2023-04-01], ~D[2023-04-30]) suggested_plan = :enterprise usage = %{ penultimate_cycle: %{date_range: penultimate_cycle, total: 12_300}, last_cycle: %{date_range: last_cycle, total: 32_100} } %{html_body: html_body} = PlausibleWeb.Email.over_limit_email(user, team, usage, suggested_plan) refute html_body =~ "Click here to upgrade your subscription" assert html_body =~ "Your usage exceeds our standard plans, so please reply back" end end describe "dashboard_locked/3" do test "renders usage, suggested plan, and links to upgrade and account settings" do user = build(:user) team = build(:team, identifier: Ecto.UUID.generate()) penultimate_cycle = Date.range(~D[2023-03-01], ~D[2023-03-31]) last_cycle = Date.range(~D[2023-04-01], ~D[2023-04-30]) usage = %{ penultimate_cycle: %{date_range: penultimate_cycle, total: 12_300}, last_cycle: %{date_range: last_cycle, total: 32_100} } %{html_body: html_body, subject: subject} = PlausibleWeb.Email.dashboard_locked(user, team, usage, "100k") assert subject == "[Action required] Your Plausible dashboard is now locked" assert html_body =~ PlausibleWeb.TextHelpers.format_date_range(last_cycle) assert html_body =~ "We recommend you upgrade to the 100k pageviews/month plan" assert html_body =~ "your account recorded 32,100 billable pageviews" assert html_body =~ "cycle before that (#{PlausibleWeb.TextHelpers.format_date_range(penultimate_cycle)}), the usage was 12,300 billable pageviews" assert text_of_element( html_body, ~s|a[href$="/billing/choose-plan?__team=#{team.identifier}"]| ) == "Click here to upgrade your subscription" assert text_of_element( html_body, ~s|a[href$="/settings/billing/subscription?__team=#{team.identifier}"]| ) == "account settings" assert html_body =~ PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{team.identifier}" end test "asks enterprise level usage to contact us" do user = build(:user) team = build(:team, identifier: Ecto.UUID.generate()) penultimate_cycle = Date.range(~D[2023-03-01], ~D[2023-03-31]) last_cycle = Date.range(~D[2023-04-01], ~D[2023-04-30]) suggested_plan = :enterprise usage = %{ penultimate_cycle: %{date_range: penultimate_cycle, total: 12_300}, last_cycle: %{date_range: last_cycle, total: 32_100} } %{html_body: html_body} = PlausibleWeb.Email.dashboard_locked(user, team, usage, suggested_plan) refute html_body =~ "Click here to upgrade your subscription" assert html_body =~ "Your usage exceeds our standard plans, so please reply back" end end describe "enterprise_over_limit_internal_email/4" do test "renders pageview usage by billing cycles + sites usage/limit" do user = build(:user) penultimate_cycle = Date.range(~D[2023-03-01], ~D[2023-03-31]) last_cycle = Date.range(~D[2023-04-01], ~D[2023-04-30]) pageview_usage = %{ penultimate_cycle: %{date_range: penultimate_cycle, total: 100_141_888}, last_cycle: %{date_range: last_cycle, total: 100_222_999} } %{html_body: html_body, subject: subject} = PlausibleWeb.Email.enterprise_over_limit_internal_email(user, pageview_usage, 80, 50) assert subject == "#{user.email} has outgrown their enterprise plan" assert html_body =~ "Last billing cycle: #{PlausibleWeb.TextHelpers.format_date_range(last_cycle)}" assert html_body =~ "Last cycle pageview usage: 100,222,999 billable pageviews" assert html_body =~ "Penultimate billing cycle: #{PlausibleWeb.TextHelpers.format_date_range(penultimate_cycle)}" assert html_body =~ "Penultimate cycle pageview usage: 100,141,888 billable pageviews" assert html_body =~ "Site usage: 80 / 50 allowed sites" end end describe "approaching accept_traffic_until" do test "renders first warning" do user = build(:user, id: 123, name: "John Doe") team = build(:team, identifier: Ecto.UUID.generate()) notification = %{ id: user.id, email: user.email, deadline: Date.add(Date.utc_today(), 7), site_ids: [1, 2, 3], name: user.name, team: team } %{html_body: body, subject: subject} = PlausibleWeb.Email.approaching_accept_traffic_until(notification) assert subject == "Your stats stop collecting soon" assert body =~ plausible_link(team: team, label: "start a Plausible subscription") assert body =~ "Hey John," assert body =~ "Your sites are still sending us data, but your account is no longer active. We'll stop counting your stats next week." end test "renders final warning" do user = build(:user, id: 123, name: "John Doe") team = build(:team, identifier: Ecto.UUID.generate()) notification = %{ id: user.id, email: user.email, deadline: Date.add(Date.utc_today(), 1), site_ids: [1, 2, 3], name: user.name, team: team } %{html_body: body, subject: subject} = PlausibleWeb.Email.approaching_accept_traffic_until_tomorrow(notification) assert subject == "Your stats stop tomorrow" assert body =~ plausible_link(team: team, label: "start a Plausible subscription") assert body =~ "Your sites are still sending us data, but your account is no longer active. We'll stop counting your stats tomorrow." end end describe "site setup emails" do setup do trial_user = new_user(trial_expiry_date: Date.add(Date.utc_today(), 100)) site = new_site(owner: trial_user) emails = [ PlausibleWeb.Email.create_site_email(trial_user), PlausibleWeb.Email.site_setup_help(trial_user, site.team, site), PlausibleWeb.Email.site_setup_success(trial_user, site) ] {:ok, emails: emails, user: trial_user, site: site} end test "create site email", %{user: user} do email = PlausibleWeb.Email.create_site_email(user) assert email.html_body =~ "You’ve created your Plausible account but haven’t added a site yet." end test "site setup help email", %{user: user, site: site} do email = PlausibleWeb.Email.site_setup_help(user, site.team, site) assert email.html_body =~ "We haven't recorded any traffic for #{site.domain} yet." end test "site setup success email", %{user: user, site: site} do email = PlausibleWeb.Email.site_setup_success(user, site) assert email.html_body =~ "Your first visitor is now visible in Plausible." end end describe "import emails" do setup do user = new_user() site = new_site(owner: user) {:ok, user: user, site: site} end for {source, success_phrase, failure_phrase} <- [ {:google_analytics_4, "Your Google Analytics import has completed successfully", "Unfortunately, your Google Analytics import for"}, {:universal_analytics, "Your Google Analytics import has completed successfully", "Unfortunately, your Google Analytics import for"}, {:csv, "Your CSV import has completed successfully", "Unfortunately, your CSV import for"} ] do test "success email for #{source}", %{user: user, site: site} do site_import = insert(:site_import, site: site, source: unquote(source)) PlausibleWeb.Email.import_success(site_import, user) email = PlausibleWeb.Email.import_success(site_import, user) assert email.text_body =~ unquote(success_phrase) end test "failure email for #{source}", %{user: user, site: site} do site_import = insert(:site_import, site: site, source: unquote(source)) email = PlausibleWeb.Email.import_failure(site_import, user) assert email.text_body =~ unquote(failure_phrase) end end end describe "text_body" do test "welcome email" do email = Email.base_email() |> Email.render("welcome_email.html", %{ user: build(:user, name: "John Doe"), code: "123" }) assert email.text_body =~ "Welcome to Plausible." end end def plausible_url do PlausibleWeb.EmailView.plausible_url() end def plausible_link(opts \\ []) do suffix = if team = Keyword.get(opts, :team) do "?__team=#{team.identifier}" else "" end plausible_url = plausible_url() label = if label = Keyword.get(opts, :label) do label else plausible_url end " suffix}\">#{label}" end end