Changes to the emails as discussed (#3540)

* Update over_limit.html.eex

* Update dashboard_locked.html.eex

* Update dashboard_locked.html.eex

* Update over_limit.html.eex

* Update dashboard_locked.html.eex

* fix tests

* stop querying owned_site_ids three times

... when querying for billing cycles. Adds an optional `owned_site_ids`
argument to the `usage_cycle` function.

* add penultimate billing cycle info to emails

This commit also refactors some code and adds unit tests to email templates

* use delimit_integer instead of large_number_format

... to display usage with exact numbers such as 1,099,999 instead of 1M

* add penultimate cycle date ranges and linebreaks

---------

Co-authored-by: RobertJoonas <56999674+RobertJoonas@users.noreply.github.com>
Co-authored-by: Robert Joonas <robertjoonas16@gmail.com>
This commit is contained in:
Marko Saric 2023-12-06 13:02:22 +01:00 committed by GitHub
parent 615b6aef7d
commit 0b00762591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 190 additions and 93 deletions

View File

@ -128,9 +128,11 @@ defmodule Plausible.Billing.Quota do
active_subscription? = Plausible.Billing.subscription_is_active?(user.subscription)
if active_subscription? && user.subscription.last_bill_date do
owned_site_ids = Plausible.Sites.owned_site_ids(user)
[:current_cycle, :last_cycle, :penultimate_cycle]
|> Task.async_stream(fn cycle ->
%{cycle => usage_cycle(user, cycle)}
%{cycle => usage_cycle(user, cycle, owned_site_ids)}
end)
|> Enum.map(fn {:ok, cycle_usage} -> cycle_usage end)
|> Enum.reduce(%{}, &Map.merge/2)
@ -139,17 +141,19 @@ defmodule Plausible.Billing.Quota do
end
end
@spec usage_cycle(User.t(), period(), Date.t()) :: usage_cycle()
@spec usage_cycle(User.t(), period(), list() | nil, Date.t()) :: usage_cycle()
def usage_cycle(user, cycle, today \\ Timex.today())
def usage_cycle(user, cycle, owned_site_ids \\ nil, today \\ Timex.today())
def usage_cycle(user, :last_30_days, today) do
def usage_cycle(user, cycle, nil, today) do
usage_cycle(user, cycle, Plausible.Sites.owned_site_ids(user), today)
end
def usage_cycle(_user, :last_30_days, owned_site_ids, today) do
date_range = Date.range(Timex.shift(today, days: -30), today)
{pageviews, custom_events} =
user
|> Plausible.Sites.owned_site_ids()
|> Plausible.Stats.Clickhouse.usage_breakdown(date_range)
Plausible.Stats.Clickhouse.usage_breakdown(owned_site_ids, date_range)
%{
date_range: date_range,
@ -159,7 +163,7 @@ defmodule Plausible.Billing.Quota do
}
end
def usage_cycle(user, cycle, today) do
def usage_cycle(user, cycle, owned_site_ids, today) do
user = Plausible.Users.with_subscription(user)
last_bill_date = user.subscription.last_bill_date
@ -188,9 +192,7 @@ defmodule Plausible.Billing.Quota do
end
{pageviews, custom_events} =
user
|> Plausible.Sites.owned_site_ids()
|> Plausible.Stats.Clickhouse.usage_breakdown(date_range)
Plausible.Stats.Clickhouse.usage_breakdown(owned_site_ids, date_range)
%{
date_range: date_range,

View File

@ -68,19 +68,10 @@ defmodule Plausible.Billing.SiteLocker do
@spec send_grace_period_end_email(Plausible.Auth.User.t()) :: Plausible.Mailer.result()
def send_grace_period_end_email(user) do
last_cycle_usage =
Plausible.Billing.Quota.usage_cycle(user, :last_cycle)
usage = Plausible.Billing.Quota.monthly_pageview_usage(user)
suggested_plan = Plausible.Billing.Plans.suggest(user, usage.last_cycle.total)
suggested_plan = Plausible.Billing.Plans.suggest(user, last_cycle_usage.total)
template =
PlausibleWeb.Email.dashboard_locked(
user,
last_cycle_usage.total,
last_cycle_usage.date_range,
suggested_plan
)
Plausible.Mailer.send(template)
PlausibleWeb.Email.dashboard_locked(user, usage, suggested_plan)
|> Plausible.Mailer.send()
end
end

View File

@ -139,7 +139,7 @@ defmodule PlausibleWeb.Email do
})
end
def over_limit_email(user, usage, last_cycle, suggested_plan) do
def over_limit_email(user, usage, suggested_plan) do
priority_email()
|> to(user)
|> tag("over-limit")
@ -147,26 +147,24 @@ defmodule PlausibleWeb.Email do
|> render("over_limit.html", %{
user: user,
usage: usage,
last_cycle: last_cycle,
suggested_plan: suggested_plan
})
end
def enterprise_over_limit_internal_email(user, usage, last_cycle, site_usage, site_allowance) do
def enterprise_over_limit_internal_email(user, pageview_usage, site_usage, site_allowance) do
base_email(%{layout: nil})
|> to("enterprise@plausible.io")
|> tag("enterprise-over-limit")
|> subject("#{user.email} has outgrown their enterprise plan")
|> render("enterprise_over_limit_internal.html", %{
user: user,
usage: usage,
last_cycle: last_cycle,
pageview_usage: pageview_usage,
site_usage: site_usage,
site_allowance: site_allowance
})
end
def dashboard_locked(user, usage, last_cycle, suggested_plan) do
def dashboard_locked(user, usage, suggested_plan) do
priority_email()
|> to(user)
|> tag("dashboard-locked")
@ -174,7 +172,6 @@ defmodule PlausibleWeb.Email do
|> render("dashboard_locked.html", %{
user: user,
usage: usage,
last_cycle: last_cycle,
suggested_plan: suggested_plan
})
end

View File

@ -1,18 +1,15 @@
Last week we sent a reminder that your traffic has exceeded your Plausible Analytics subscription tier. As we haven't heard from you, we've now locked your stats so they're no longer accessible.
Last week we sent a reminder that your site traffic has exceeded the limits of your Plausible Analytics subscription tier for two consecutive months. Since we haven't received a response, we've had to temporarily lock access to your stats.
<br /><br />
Your subscription is still active, we're still counting your stats and haven't deleted any of your data but as you have outgrown your subscription tier, we kindly ask you to upgrade to accommodate your new traffic levels. We will unlock your dashboard immediately after your account has been upgraded to the appropriate tier.
Your subscription is still active, we're still counting your stats and haven't deleted any of your data but as you have outgrown your subscription tier, we kindly ask you to upgrade to match your new traffic levels. Upon upgrading to a suitable tier, your dashboard access will be immediately restored.
<br /><br />
During the last billing cycle (<%= PlausibleWeb.TextHelpers.format_date_range(@usage.last_cycle.date_range) %>), your account recorded <%= PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total) %> billable pageviews. In the billing cycle before that (<%= PlausibleWeb.TextHelpers.format_date_range(@usage.penultimate_cycle.date_range) %>), the usage was <%= PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total) %> billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your <%= link("account settings", to: plausible_url() <> PlausibleWeb.Router.Helpers.auth_path(PlausibleWeb.Endpoint, :user_settings)) %>, you'll find an overview of your usage and limits.
<br /><br />
In the last billing cycle (<%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %>), your account has used <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews.
<%= if @suggested_plan == :enterprise do %>
This is more than our standard plans, so please reply back to this email to get a quote for your volume.
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %>
Based on that we recommend you select a <%= @suggested_plan.volume %>/mo plan.
<br /><br />
<a href="https://plausible.io/settings">Click here</a> to go to your account settings. You can upgrade your subscription tier by clicking the 'Change plan' link.
<%= link("Click here to upgrade your subscription", to: plausible_url() <> Plausible.Billing.upgrade_route_for(@user)) %>. We recommend you upgrade to the <%= @suggested_plan.volume %>/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<% end %>
<br /><br />
The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<br /><br />
Do you have questions or need help with anything? Just reply to this email and we'll gladly help.
Do you have questions or need help with anything? Just reply to this email. We're here to help!
<br /><br />
Thanks for understanding and for being a Plausible subscriber!

View File

@ -1,6 +1,8 @@
Automated notice about an enterprise account that has gone over their limits. <br /><br />
Customer email: <%= @user.email %><br />
Last billing cycle: <%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %><br >
Pageview Usage: <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews<br />
Last billing cycle: <%= PlausibleWeb.TextHelpers.format_date_range(@pageview_usage.last_cycle.date_range) %><br >
Last cycle pageview usage: <%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_cycle.total) %> billable pageviews<br />
Penultimate billing cycle: <%= PlausibleWeb.TextHelpers.format_date_range(@pageview_usage.penultimate_cycle.date_range) %><br >
Penultimate cycle pageview usage: <%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.penultimate_cycle.total) %> billable pageviews<br />
Site usage: <%= @site_usage %> / <%= @site_allowance %> allowed sites<br />

View File

@ -1,20 +1,17 @@
Thanks for being a Plausible Analytics subscriber!
<br /><br />
This is a notice that your traffic has exceeded your subscription tier. Congrats on all that traffic!
This is a friendly reminder that your traffic has exceeded your subscription tier for two consecutive months. Congrats on all that traffic!
<br /><br />
To keep having access to your stats, we require you to upgrade your account to accommodate your new traffic levels. If you do not upgrade your account within the next 7 days, we will lock your stats and they won't be accessible.
To maintain uninterrupted access to your stats, we kindly ask you to upgrade your account to match your new traffic levels. Please note, if your account isn't upgraded within the next 7 days, access to your stats will be temporarily locked.
<br /><br />
During the last billing cycle (<%= PlausibleWeb.TextHelpers.format_date_range(@usage.last_cycle.date_range) %>), your account recorded <%= PlausibleWeb.AuthView.delimit_integer(@usage.last_cycle.total) %> billable pageviews. In the billing cycle before that (<%= PlausibleWeb.TextHelpers.format_date_range(@usage.penultimate_cycle.date_range) %>), your account used <%= PlausibleWeb.AuthView.delimit_integer(@usage.penultimate_cycle.total) %> billable pageviews. Note that billable pageviews include both standard pageviews and custom events. In your <%= link("account settings", to: plausible_url() <> PlausibleWeb.Router.Helpers.auth_path(PlausibleWeb.Endpoint, :user_settings)) %>, you'll find an overview of your usage and limits.
<br /><br />
In the last billing cycle (<%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %>), your account has used <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews.
<%= if @suggested_plan == :enterprise do %>
This is more than our standard plans, so please reply back to this email to get a quote for your volume.
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %>
Based on that we recommend you select a <%= @suggested_plan.volume %>/mo plan.
<br /><br />
You can upgrade your subscription using our self-serve platform. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<br /><br />
<a href="https://plausible.io/settings">Click here</a> to go to your account settings. You can upgrade your subscription tier by clicking the 'Change plan' link.
<%= link("Click here to upgrade your subscription", to: plausible_url() <> Plausible.Billing.upgrade_route_for(@user)) %>. We recommend you upgrade to the <%= @suggested_plan.volume %>/mo plan. The new charge will be prorated to reflect the amount you have already paid and the time until your current subscription is supposed to expire.
<% end %>
<br /><br />
Do you have questions or need help with anything? Just reply to this email and we'll gladly help.
Do you have questions or need help with anything? Just reply to this email. We're here to help!
<br /><br />
Thanks again for using our product and for your support!

View File

@ -73,41 +73,32 @@ defmodule Plausible.Workers.CheckUsage do
{{:below_limit, _}, {:below_limit, _}} ->
nil
{{_, {last_cycle, last_cycle_usage}}, {_, {site_usage, site_allowance}}} ->
template =
PlausibleWeb.Email.enterprise_over_limit_internal_email(
subscriber,
last_cycle_usage,
last_cycle,
site_usage,
site_allowance
)
Plausible.Mailer.send(template)
{{_, pageview_usage}, {_, {site_usage, site_allowance}}} ->
PlausibleWeb.Email.enterprise_over_limit_internal_email(
subscriber,
pageview_usage,
site_usage,
site_allowance
)
|> Plausible.Mailer.send()
subscriber
|> Plausible.Auth.GracePeriod.start_manual_lock_changeset(last_cycle_usage)
|> Plausible.Auth.GracePeriod.start_manual_lock_changeset(pageview_usage.last_cycle.total)
|> Repo.update()
end
end
defp check_regular_subscriber(subscriber, quota_mod) do
case check_pageview_usage(subscriber, quota_mod) do
{:over_limit, {last_cycle, last_cycle_usage}} ->
suggested_plan = Plausible.Billing.Plans.suggest(subscriber, last_cycle_usage)
{:over_limit, pageview_usage} ->
suggested_plan =
Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total)
template =
PlausibleWeb.Email.over_limit_email(
subscriber,
last_cycle_usage,
last_cycle,
suggested_plan
)
Plausible.Mailer.send(template)
PlausibleWeb.Email.over_limit_email(subscriber, pageview_usage, suggested_plan)
|> Plausible.Mailer.send()
subscriber
|> Plausible.Auth.GracePeriod.start_changeset(last_cycle_usage)
|> Plausible.Auth.GracePeriod.start_changeset(pageview_usage.last_cycle.total)
|> Repo.update()
_ ->
@ -120,9 +111,9 @@ defmodule Plausible.Workers.CheckUsage do
limit = Quota.monthly_pageview_limit(subscriber.subscription)
if exceeds_last_two_usage_cycles?(usage, limit) do
{:over_limit, {usage.last_cycle.date_range, usage.last_cycle.total}}
{:over_limit, usage}
else
{:below_limit, {usage.last_cycle.date_range, usage.last_cycle.total}}
{:below_limit, usage}
end
end

View File

@ -654,13 +654,13 @@ defmodule Plausible.Billing.QuotaTest do
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
assert %{date_range: penultimate_cycle, pageviews: 2, custom_events: 3, total: 5} =
Quota.usage_cycle(user, :penultimate_cycle, today)
Quota.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.usage_cycle(user, :last_cycle, today)
Quota.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle, pageviews: 0, custom_events: 3, total: 3} =
Quota.usage_cycle(user, :current_cycle, today)
Quota.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2023-04-03], ~D[2023-05-02])
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
@ -671,7 +671,7 @@ defmodule Plausible.Billing.QuotaTest do
today = ~D[2023-06-01]
assert %{date_range: last_30_days, pageviews: 4, custom_events: 1, total: 5} =
Quota.usage_cycle(user, :last_30_days, today)
Quota.usage_cycle(user, :last_30_days, nil, today)
assert last_30_days == Date.range(~D[2023-05-02], ~D[2023-06-01])
end
@ -694,7 +694,7 @@ defmodule Plausible.Billing.QuotaTest do
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.usage_cycle(user, :last_cycle, today)
Quota.usage_cycle(user, :last_cycle, nil, today)
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
end
@ -706,13 +706,13 @@ defmodule Plausible.Billing.QuotaTest do
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
assert %{date_range: penultimate_cycle} =
Quota.usage_cycle(user, :penultimate_cycle, today)
Quota.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle} =
Quota.usage_cycle(user, :last_cycle, today)
Quota.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle} =
Quota.usage_cycle(user, :current_cycle, today)
Quota.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])
assert last_cycle == Date.range(~D[2021-01-01], ~D[2021-01-31])
@ -726,13 +726,13 @@ defmodule Plausible.Billing.QuotaTest do
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
assert %{date_range: penultimate_cycle, total: 0} =
Quota.usage_cycle(user, :penultimate_cycle, today)
Quota.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, total: 0} =
Quota.usage_cycle(user, :last_cycle, today)
Quota.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle, total: 0} =
Quota.usage_cycle(user, :current_cycle, today)
Quota.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2020-11-01], ~D[2020-11-30])
assert last_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])

View File

@ -116,6 +116,127 @@ defmodule PlausibleWeb.EmailTest do
end
end
describe "over_limit_email/3" do
test "renders usage, suggested plan, and links to upgrade and account settings" 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])
suggested_plan = %Plausible.Billing.Plan{volume: "100k"}
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, usage, suggested_plan)
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/mo 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 html_body =~ "/billing/choose-plan\">Click here to upgrade your subscription</a>"
assert html_body =~ "/settings\">account settings</a>"
end
test "asks enterprise level usage to contact us" 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])
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, 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)
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 = %Plausible.Billing.Plan{volume: "100k"}
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, usage, suggested_plan)
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/mo 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 html_body =~ "/billing/choose-plan\">Click here to upgrade your subscription</a>"
assert html_body =~ "/settings\">account settings</a>"
end
test "asks enterprise level usage to contact us" 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])
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, 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
def plausible_link() do
plausible_url = PlausibleWeb.EmailView.plausible_url()
"<a href=\"#{plausible_url}\">#{plausible_url}</a>"

View File

@ -127,7 +127,7 @@ defmodule Plausible.Workers.CheckUsageTest do
assert_delivered_email_matches(%{html_body: html_body})
assert html_body =~
"This is more than our standard plans, so please reply back to this email to get a quote for your volume."
"Your usage exceeds our standard plans, so please reply back to this email for a tailored quote"
end
test "skips checking users who already have a grace period", %{user: user} do
@ -178,8 +178,7 @@ defmodule Plausible.Workers.CheckUsageTest do
html_body: html_body
})
# Should find 2 visiors
assert html_body =~ ~s(Based on that we recommend you select a 100k/mo plan.)
assert html_body =~ "We recommend you upgrade to the 100k/mo plan"
end
describe "enterprise customers" do