Append `__team` parameter to URLs in notification e-mails (#5266)

* Append `__team` parameter to URLs in notification e-mails

* Improve copy in guest to team member promotion email

* Make "return_to" set in redirect in `RequireAccountPlug` actually work

* Improve accepted site transfer e-mail phrasing and link
This commit is contained in:
Adrian Gruntkowski 2025-04-02 16:38:52 +02:00 committed by GitHub
parent 98ffeca6a6
commit 299e59afc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 266 additions and 108 deletions

View File

@ -67,7 +67,7 @@ defmodule Plausible.Billing.SiteLocker do
for recipient <- team.owners ++ team.billing_members do
recipient
|> PlausibleWeb.Email.dashboard_locked(usage, suggested_plan)
|> PlausibleWeb.Email.dashboard_locked(team, usage, suggested_plan)
|> Plausible.Mailer.send()
end
end

View File

@ -103,7 +103,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
:ok <- check_can_transfer_site(new_team, new_owner),
:ok <- Teams.Invitations.ensure_can_take_ownership(site, new_team),
:ok <- Teams.Invitations.accept_site_transfer(site_transfer, new_team) do
Teams.Invitations.send_transfer_accepted_email(site_transfer)
Teams.Invitations.send_transfer_accepted_email(site_transfer, new_team)
site = site |> Repo.reload!() |> Repo.preload(ownerships: :user)

View File

@ -79,10 +79,11 @@ defmodule Plausible.Teams.Invitations do
from gi in Teams.GuestInvitation,
inner_join: s in assoc(gi, :site),
inner_join: ti in assoc(gi, :team_invitation),
inner_join: t in assoc(ti, :team),
inner_join: inviter in assoc(ti, :inviter),
where: gi.invitation_id == ^guest_invitation_id,
where: ti.email == ^user.email,
preload: [site: s, team_invitation: {ti, inviter: inviter}]
preload: [site: s, team_invitation: {ti, team: t, inviter: inviter}]
case Repo.one(invitation_query) do
nil ->
@ -97,7 +98,7 @@ defmodule Plausible.Teams.Invitations do
transfer =
Teams.SiteTransfer
|> Repo.get_by(transfer_id: transfer_id, email: user.email)
|> Repo.preload([:site, :initiator])
|> Repo.preload([:initiator, site: :team])
case transfer do
nil ->
@ -459,11 +460,20 @@ defmodule Plausible.Teams.Invitations do
end
end
def send_transfer_accepted_email(site_transfer) do
def send_transfer_accepted_email(site_transfer, team) do
initiator_as_editor? =
Teams.Memberships.site_role(site_transfer.site, site_transfer.initiator) == {:ok, :editor}
initiator_as_guest? =
Teams.Memberships.team_role(team, site_transfer.initiator) == {:ok, :guest}
PlausibleWeb.Email.ownership_transfer_accepted(
site_transfer.email,
site_transfer.initiator.email,
site_transfer.site
team,
site_transfer.site,
initiator_as_editor?,
initiator_as_guest?
)
|> Plausible.Mailer.send()
end
@ -728,7 +738,11 @@ defmodule Plausible.Teams.Invitations do
defp send_invitation_accepted_email(team_invitation, [guest_invitation | _]) do
team_invitation.inviter.email
|> PlausibleWeb.Email.guest_invitation_accepted(team_invitation.email, guest_invitation.site)
|> PlausibleWeb.Email.guest_invitation_accepted(
team_invitation.email,
team_invitation.team,
guest_invitation.site
)
|> Plausible.Mailer.send()
end

View File

@ -31,14 +31,15 @@ defmodule PlausibleWeb.Email do
|> render("create_site_email.html", user: user)
end
def site_setup_help(user, site) do
def site_setup_help(user, team, site) do
base_email()
|> to(user)
|> tag("help-email")
|> subject("Your Plausible setup: Waiting for the first page views")
|> render("site_setup_help_email.html",
user: user,
site: site
site: site,
site_team: team
)
end
@ -86,21 +87,22 @@ defmodule PlausibleWeb.Email do
|> render("two_factor_disabled_email.html", user: user)
end
def trial_one_week_reminder(user) do
def trial_one_week_reminder(user, team) do
base_email()
|> to(user)
|> tag("trial-one-week-reminder")
|> subject("Your Plausible trial expires next week")
|> render("trial_one_week_reminder.html", user: user)
|> render("trial_one_week_reminder.html", user: user, team: team)
end
def trial_upgrade_email(user, day, usage, suggested_plan) do
def trial_upgrade_email(user, team, day, usage, suggested_plan) do
base_email()
|> to(user)
|> tag("trial-upgrade-email")
|> subject("Your Plausible trial ends #{day}")
|> render("trial_upgrade_email.html",
user: user,
team: team,
day: day,
custom_events: usage.custom_events,
usage: usage.total,
@ -108,13 +110,14 @@ defmodule PlausibleWeb.Email do
)
end
def trial_over_email(user) do
def trial_over_email(user, team) do
base_email()
|> to(user)
|> tag("trial-over-email")
|> subject("Your Plausible trial has ended")
|> render("trial_over_email.html",
user: user,
team: team,
extra_offset: Plausible.Teams.Team.trial_accept_traffic_until_offset_days()
)
end
@ -154,13 +157,14 @@ defmodule PlausibleWeb.Email do
})
end
def over_limit_email(user, usage, suggested_plan) do
def over_limit_email(user, team, usage, suggested_plan) do
priority_email()
|> to(user)
|> tag("over-limit")
|> subject("[Action required] You have outgrown your Plausible subscription tier")
|> render("over_limit.html", %{
user: user,
team: team,
usage: usage,
suggested_plan: suggested_plan
})
@ -179,13 +183,14 @@ defmodule PlausibleWeb.Email do
})
end
def dashboard_locked(user, usage, suggested_plan) do
def dashboard_locked(user, team, usage, suggested_plan) do
priority_email()
|> to(user)
|> tag("dashboard-locked")
|> subject("[Action required] Your Plausible dashboard is now locked")
|> render("dashboard_locked.html", %{
user: user,
team: team,
usage: usage,
suggested_plan: suggested_plan
})
@ -200,6 +205,7 @@ defmodule PlausibleWeb.Email do
|> subject("Your Plausible subscription is up for renewal")
|> render("yearly_renewal_notification.html", %{
user: owner,
team: team,
date: date,
next_bill_amount: team.subscription.next_bill_amount,
currency: team.subscription.currency_code
@ -220,6 +226,7 @@ defmodule PlausibleWeb.Email do
|> subject("Your Plausible subscription is about to expire")
|> render("yearly_expiration_notification.html", %{
user: owner,
team: team,
next_bill_date: next_bill_date,
accept_traffic_until: accept_traffic_until
})
@ -300,7 +307,7 @@ defmodule PlausibleWeb.Email do
)
end
def guest_invitation_accepted(inviter_email, invitee_email, site) do
def guest_invitation_accepted(inviter_email, invitee_email, team, site) do
priority_email()
|> to(inviter_email)
|> tag("guest-invitation-accepted")
@ -309,6 +316,7 @@ defmodule PlausibleWeb.Email do
)
|> render("guest_invitation_accepted.html",
invitee_email: invitee_email,
team: team,
site: site
)
end
@ -334,6 +342,7 @@ defmodule PlausibleWeb.Email do
"[#{Plausible.product_name()}] #{guest_invitation.team_invitation.email} rejected your invitation to #{guest_invitation.site.domain}"
)
|> render("guest_invitation_rejected.html",
team: guest_invitation.team_invitation.team,
guest_invitation: guest_invitation
)
end
@ -346,11 +355,19 @@ defmodule PlausibleWeb.Email do
"[#{Plausible.product_name()}] #{team_invitation.email} rejected your invitation to \"#{team_invitation.team.name}\" team"
)
|> render("team_invitation_rejected.html",
team: team_invitation.team,
team_invitation: team_invitation
)
end
def ownership_transfer_accepted(new_owner_email, inviter_email, site) do
def ownership_transfer_accepted(
new_owner_email,
inviter_email,
team,
site,
initiator_as_editor?,
initiator_as_guest?
) do
priority_email()
|> to(inviter_email)
|> tag("ownership-transfer-accepted")
@ -359,7 +376,10 @@ defmodule PlausibleWeb.Email do
)
|> render("ownership_transfer_accepted.html",
new_owner_email: new_owner_email,
site: site
team: team,
site: site,
initiator_as_editor?: initiator_as_editor?,
initiator_as_guest?: initiator_as_guest?
)
end
@ -372,6 +392,7 @@ defmodule PlausibleWeb.Email do
)
|> render("ownership_transfer_rejected.html",
user: site_transfer.initiator,
team: site_transfer.site.team,
site_transfer: site_transfer
)
end
@ -405,6 +426,7 @@ defmodule PlausibleWeb.Email do
def import_success(site_import, user) do
import_api = Plausible.Imported.ImportSources.by_name(site_import.source)
label = import_api.label()
team = site_import.site.team
priority_email()
|> to(user)
@ -413,7 +435,9 @@ defmodule PlausibleWeb.Email do
|> render(import_api.email_template(), %{
site_import: site_import,
label: label,
link: PlausibleWeb.Endpoint.url() <> "/" <> URI.encode_www_form(site_import.site.domain),
link:
PlausibleWeb.Endpoint.url() <>
"/" <> URI.encode_www_form(site_import.site.domain) <> "?__team=#{team.identifier}",
user: user,
success: true
})
@ -449,7 +473,7 @@ defmodule PlausibleWeb.Email do
PlausibleWeb.Endpoint,
:download_export,
site.domain
)
) <> "?__team=#{site.team.identifier}"
priority_email()
|> to(user)
@ -492,7 +516,8 @@ defmodule PlausibleWeb.Email do
|> subject("We'll stop counting your stats")
|> render("approaching_accept_traffic_until.html",
time: "next week",
user: %{email: notification.email, name: notification.name}
user: %{email: notification.email, name: notification.name},
team: notification.team
)
end
@ -503,7 +528,8 @@ defmodule PlausibleWeb.Email do
|> subject("A reminder that we'll stop counting your stats tomorrow")
|> render("approaching_accept_traffic_until.html",
time: "tomorrow",
user: %{email: notification.email, name: notification.name}
user: %{email: notification.email, name: notification.name},
team: notification.team
)
end

View File

@ -33,8 +33,15 @@ defmodule PlausibleWeb.RequireAccountPlug do
end
end
defp redirect_to(%Plug.Conn{method: :get} = conn) do
Routes.auth_path(conn, :login_form, return_to: conn.request_path)
defp redirect_to(%Plug.Conn{method: "GET"} = conn) do
return_to =
if conn.query_string && String.length(conn.query_string) > 0 do
conn.request_path <> "?" <> conn.query_string
else
conn.request_path
end
Routes.auth_path(conn, :login_form, return_to: return_to)
end
defp redirect_to(conn), do: Routes.auth_path(conn, :login_form)

View File

@ -2,6 +2,6 @@ You used to have an active account with {Plausible.product_name()}, a simple, li
<br /><br />
We've noticed that you're still sending us stats so we're writing to inform you that we'll stop accepting stats from your sites {@time}. We're an independent, bootstrapped service and we don't sell your data, so this will reduce our server costs and help keep us sustainable.
<br /><br /> If you'd like to continue counting your site stats in a privacy-friendly way, please
<a href={plausible_url()}>login to your Plausible account</a> and start a subscription.
<a href={plausible_url() <> "?__team=#{@team.identifier}"}>login to your Plausible account</a> and start a subscription.
<br /><br />
Do you have any questions or need help with anything? Just reply to this email and we'll gladly help.

View File

@ -7,12 +7,12 @@ During the last billing cycle ({PlausibleWeb.TextHelpers.format_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
<a href={PlausibleWeb.Router.Helpers.settings_url(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits.
<a href={PlausibleWeb.Router.Helpers.settings_url(PlausibleWeb.Endpoint, :subscription) <> "?__team=#{@team.identifier}"}>account settings</a>, you'll find an overview of your usage and limits.
<br /><br />
<%= if @suggested_plan == :enterprise do %>
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. 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.
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>Click here to upgrade your subscription</a>. 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.
<br /><br />
If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments.
<% end %>

View File

@ -1,3 +1,3 @@
{@inviter.email} has invited you to the {@site.domain} site on {Plausible.product_name()}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view and respond to the invitation. The invitation
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index) <> "?__team=none"}>Click here</a> to view and respond to the invitation. The invitation
will expire 48 hours after this email is sent.

View File

@ -1,2 +1,2 @@
{@invitee_email} has accepted your invitation to {@site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain) <> "?__team=#{@team.identifier}"}>Click here</a> to view site settings.

View File

@ -1,2 +1,2 @@
{@guest_invitation.team_invitation.email} has rejected your invitation to {@guest_invitation.site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @guest_invitation.site.domain)}>Click here</a> to view site settings.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @guest_invitation.site.domain) <> "?__team=#{@team.identifier}"}>Click here</a> to view site settings.

View File

@ -1 +1,2 @@
{@inviter.email} has promoted you to a team member in the "{@team.name}" team on {Plausible.product_name()}.
<a href={PlausibleWeb.Router.Helpers.site_url(PlausibleWeb.Endpoint, :index) <> "?__team=#{@team.identifier}"}>Click here</a> to view sites managed by the team.

View File

@ -8,12 +8,12 @@ During the last billing cycle ({PlausibleWeb.TextHelpers.format_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
<a href={plausible_url() <> PlausibleWeb.Router.Helpers.settings_path(PlausibleWeb.Endpoint, :subscription)}>account settings</a>, you'll find an overview of your usage and limits.
<a href={plausible_url() <> PlausibleWeb.Router.Helpers.settings_path(PlausibleWeb.Endpoint, :subscription) <> "?__team=#{@team.identifier}"}>account settings</a>, you'll find an overview of your usage and limits.
<br /><br />
<%= if @suggested_plan == :enterprise do %>
Your usage exceeds our standard plans, so please reply back to this email for a tailored quote.
<% else %>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>Click here to upgrade your subscription</a>. 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.
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>Click here to upgrade your subscription</a>. 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.
<br /><br />
If your usage decreases in the future, you can switch to a lower plan at any time. Any credit balance will automatically apply to future payments.
<% end %>

View File

@ -1,3 +1,15 @@
{@new_owner_email} has accepted the ownership transfer of {@site.domain}. They will be responsible for billing of it going
forward and your role has been changed to <b>admin</b>.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain)}>Click here</a> to view site settings.
forward<%= if @initiator_as_guest? and @initiator_as_editor? do %>
and your role has been changed to <b>admin</b>
<% end %>.
<%= if @initiator_as_guest? do %>
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain) <> "?__team=none"}>
Click here
</a>
to view site settings.
<% else %>
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site.domain) <> "?__team=#{@team.identifier}"}>
Click here
</a>
to view site settings.
<% end %>

View File

@ -1,2 +1,2 @@
{@site_transfer.email} has rejected the ownership transfer of {@site_transfer.site.domain}.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site_transfer.site.domain)}>Click here</a> to view site settings.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :settings_general, @site_transfer.site.domain) <> "?__team=#{@team.identifier}"}>Click here</a> to view site settings.

View File

@ -1,4 +1,4 @@
An administrator of {@guest_membership.site.domain} has removed you as a member. You won't be able to see the stats anymore.
<br /><br />
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a>
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index) <> "?__team=none"}>Click here</a>
to view your sites.

View File

@ -1,9 +1,9 @@
<%= if Plausible.ee?() and Plausible.Teams.on_trial?(@site.team) do %>
<%= if Plausible.ee?() and Plausible.Teams.on_trial?(@site_team) do %>
You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
<br /><br />
<% end %>
To finish your setup for {@site.domain}, review
<a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/installation"}>your installation</a> and start collecting visitor statistics.
<a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/installation?__team=#{@site_team.identifier}"}>your installation</a> and start collecting visitor statistics.
<br /><br />
This Plausible script is 45 times smaller than Google Analytics script so youll have a fast loading site while getting all the important traffic insights on one single page.
<br /><br /> On WordPress? We have a

View File

@ -1,7 +1,7 @@
Congrats! We've recorded the first visitor on
<a href={"https://#{@site.domain}"}><%= @site.domain %></a>. Your traffic is now being counted without compromising the user experience and privacy of your visitors.
<br /><br />
Do check out your <a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}"}>easy to use, fast-loading and privacy-friendly dashboard</a>.
Do check out your <a href={"#{plausible_url()}/#{URI.encode_www_form(@site.domain)}?__team=#{@site_team.identifier}"}>easy to use, fast-loading and privacy-friendly dashboard</a>.
<br /><br />
Something looks off? Take a look at our <a href="https://plausible.io/docs/troubleshoot-integration">installation troubleshooting guide</a>.
<br /><br />

View File

@ -1,2 +1,2 @@
{@invitee_email} has accepted your invitation to "{@team.name}" team.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general) <> "?__team=#{@team.identifier}"}>Click here</a> to view team settings.

View File

@ -1,2 +1,2 @@
{@team_invitation.email} has rejected your invitation to \"{@team_invitation.team.name}\" team.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general)}>Click here</a> to view team settings.
{@team_invitation.email} has rejected your invitation to \"{@team.name}\" team.
<a href={Routes.settings_url(PlausibleWeb.Endpoint, :team_general) <> "?__team=#{@team.identifier}"}>Click here</a> to view team settings.

View File

@ -1,2 +1,3 @@
An administrator of "{@team_membership.team.name}" team has removed you as a member. <br /><br />
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index)}>Click here</a> to view your sites.
<a href={Routes.site_url(PlausibleWeb.Endpoint, :index) <> "?__team=none"}>Click here</a>
to view your sites.

View File

@ -2,5 +2,5 @@ Time flies! Your 30-day free trial of Plausible will end next week. <br /><br />
Over the last three weeks, We hope you got to experience the potential benefits of having website stats in an easy to use dashboard while respecting the privacy of your visitors, not annoying them with the cookie and privacy notices and still having a fast loading site.
<br /><br />
In order to continue receiving valuable website traffic insights at a glance, youll need to
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>upgrade your account</a>.
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>upgrade your account</a>.
<br /><br /> If you have any questions or feedback for us, feel free to reply to this email.

View File

@ -1,7 +1,7 @@
Your free Plausible trial has now expired. Upgrade your account to continue receiving valuable website traffic insights at a glance while respecting the privacy of your visitors and still having a fast loading site.
<br /><br />
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>
Upgrade now
</a>
<br /><br /> We will keep recording stats for {@extra_offset} days to give you time to upgrade.

View File

@ -10,7 +10,7 @@ In the last month, your account has used {PlausibleWeb.AuthView.delimit_integer(
This is more than our standard plans, so please reply back to this email to get a quote for your volume.
<% else %>
Based on that we recommend you select a {@suggested_plan.volume}/mo plan. <br /><br />
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>
Upgrade now
</a>
<br /><br />

View File

@ -1,6 +1,6 @@
Time flies! This is a reminder that your annual subscription for {Plausible.product_name()} will expire on {@next_bill_date}.
<br /><br /> You need to
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan)}>renew your subscription</a> if you want to continue using Plausible to count your website stats in a privacy-friendly way.
<a href={PlausibleWeb.Router.Helpers.billing_url(PlausibleWeb.Endpoint, :choose_plan) <> "?__team=#{@team.identifier}"}>renew your subscription</a> if you want to continue using Plausible to count your website stats in a privacy-friendly way.
<br /><br />
If you don't want to continue your subscription, there's no action required. You will lose access to your dashboard on {@next_bill_date} and we'll stop accepting stats on {@accept_traffic_until}.
<br /><br />

View File

@ -3,6 +3,7 @@ Time flies! This is a reminder that your annual subscription for {Plausible.prod
)}{@next_bill_amount} from your preferred billing method. <br /><br />
There's no action required if you're happy to continue using Plausible to count your website stats in a privacy-friendly way.
<br /><br /> If you don't want to continue your subscription, you can cancel it on your
<a href={"#{plausible_url()}/settings"}>account settings page</a>. <br /><br />
<a href={"#{plausible_url()}/settings?__team=#{@team.identifier}"}>account settings page</a>.
<br /><br />
BTW, most of our subscribers come from word of mouth, so if you love Plausible, and know someone else who might find it useful, we'd appreciate if you'd let them know. Thank you!
<br /><br /> Have a question, feedback or need some guidance? Do reply back to this email.

View File

@ -42,9 +42,10 @@ defmodule Plausible.Workers.AcceptTrafficUntil do
email: u.email,
deadline: t.accept_traffic_until,
site_ids: fragment("array_agg(?.id)", s),
name: u.name
name: u.name,
team: t
},
group_by: [u.id, t.accept_traffic_until]
group_by: [u.id, t.id]
)
for notification <- notifications do

View File

@ -112,7 +112,7 @@ defmodule Plausible.Workers.CheckUsage do
Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total)
for owner <- subscriber.owners ++ subscriber.billing_members do
PlausibleWeb.Email.over_limit_email(owner, pageview_usage, suggested_plan)
PlausibleWeb.Email.over_limit_email(owner, subscriber, pageview_usage, suggested_plan)
|> Plausible.Mailer.send()
end

View File

@ -54,7 +54,7 @@ defmodule Plausible.Workers.ImportAnalytics do
end
def import_complete(site_import) do
site_import = Repo.preload(site_import, [:site, :imported_by])
site_import = Repo.preload(site_import, [:imported_by, site: :team])
PlausibleWeb.Email.import_success(site_import, site_import.imported_by)
|> Plausible.Mailer.send()

View File

@ -15,7 +15,11 @@ defmodule Plausible.Workers.NotifyExportedAnalytics do
} = args
user = Plausible.Repo.get_by!(Plausible.Auth.User, email: email_to)
site = Plausible.Repo.get!(Plausible.Site, site_id)
site =
Plausible.Site
|> Plausible.Repo.get!(site_id)
|> Plausible.Repo.preload(:team)
email =
case status do

View File

@ -104,7 +104,7 @@ defmodule Plausible.Workers.SendSiteSetupEmails do
defp send_setup_help_email(users, site) do
for user <- users do
PlausibleWeb.Email.site_setup_help(user, site)
PlausibleWeb.Email.site_setup_help(user, site.team, site)
|> Plausible.Mailer.send()
end

View File

@ -29,7 +29,7 @@ defmodule Plausible.Workers.SendTrialNotifications do
case Date.diff(team.trial_expiry_date, Date.utc_today()) do
7 ->
if Teams.has_active_sites?(team) do
send_one_week_reminder(recipients)
send_one_week_reminder(recipients, team)
end
1 ->
@ -44,7 +44,7 @@ defmodule Plausible.Workers.SendTrialNotifications do
-1 ->
if Teams.has_active_sites?(team) do
send_over_reminder(recipients)
send_over_reminder(recipients, team)
end
_ ->
@ -55,9 +55,9 @@ defmodule Plausible.Workers.SendTrialNotifications do
:ok
end
defp send_one_week_reminder(users) do
defp send_one_week_reminder(users, team) do
for user <- users do
PlausibleWeb.Email.trial_one_week_reminder(user)
PlausibleWeb.Email.trial_one_week_reminder(user, team)
|> Plausible.Mailer.send()
end
end
@ -67,7 +67,7 @@ defmodule Plausible.Workers.SendTrialNotifications do
suggested_plan = Plausible.Billing.Plans.suggest(team, usage.total)
for user <- users do
PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage, suggested_plan)
PlausibleWeb.Email.trial_upgrade_email(user, team, "tomorrow", usage, suggested_plan)
|> Plausible.Mailer.send()
end
end
@ -77,14 +77,14 @@ defmodule Plausible.Workers.SendTrialNotifications do
suggested_plan = Plausible.Billing.Plans.suggest(team, usage.total)
for user <- users do
PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
PlausibleWeb.Email.trial_upgrade_email(user, team, "today", usage, suggested_plan)
|> Plausible.Mailer.send()
end
end
defp send_over_reminder(users) do
defp send_over_reminder(users, team) do
for user <- users do
PlausibleWeb.Email.trial_over_email(user)
PlausibleWeb.Email.trial_over_email(user, team)
|> Plausible.Mailer.send()
end
end

View File

@ -25,7 +25,7 @@ defmodule Plausible.Workers.TrafficChangeNotifier do
inner_join: t in assoc(s, :team),
where: not s.locked,
where: is_nil(t.accept_traffic_until) or t.accept_traffic_until > ^today,
preload: [site: s]
preload: [site: {s, team: t}]
)
for notification <- notifications do
@ -77,7 +77,8 @@ defmodule Plausible.Workers.TrafficChangeNotifier do
defp send_spike_notification(recipient, site, stats) do
dashboard_link =
if Repo.exists?(email_match_query(site, recipient)) do
Routes.stats_url(PlausibleWeb.Endpoint, :stats, site.domain, [])
Routes.stats_url(PlausibleWeb.Endpoint, :stats, site.domain, []) <>
"?__team=#{site.team.identifier}"
end
template =
@ -95,10 +96,11 @@ defmodule Plausible.Workers.TrafficChangeNotifier do
{dashboard_link, installation_link} =
if Repo.exists?(email_match_query(site, recipient)) do
{
Routes.stats_url(PlausibleWeb.Endpoint, :stats, site.domain, []),
Routes.stats_url(PlausibleWeb.Endpoint, :stats, site.domain, []) <>
"?__team=#{site.team.identifier}",
Routes.site_url(PlausibleWeb.Endpoint, :installation, site.domain,
flow: PlausibleWeb.Flows.review()
)
) <> "&__team=#{site.team.identifier}"
}
else
{nil, nil}

View File

@ -1218,10 +1218,10 @@ defmodule Plausible.Imported.CSVImporterTest do
assert email.to == [{user.name, user.email}]
assert email.html_body =~
~s[Please click <a href="http://localhost:8000/#{URI.encode_www_form(exported_site.domain)}/download/export">here</a>]
~s[Please click <a href="http://localhost:8000/#{URI.encode_www_form(exported_site.domain)}/download/export?__team=#{exported_site.team.identifier}">here</a>]
assert email.text_body =~
~r[Please click here \(http://localhost:8000/#{URI.encode_www_form(exported_site.domain)}/download/export\) to start the download process.]
~r[Please click here \(http://localhost:8000/#{URI.encode_www_form(exported_site.domain)}/download/export\?__team=#{exported_site.team.identifier}\) to start the download process.]
context
end

View File

@ -98,7 +98,11 @@ defmodule PlausibleWeb.StatsControllerTest do
new_site(domain: "some-other-public-site.io", public: true)
conn = get(conn, conn |> get("/some-other-public-site.io") |> redirected_to())
assert redirected_to(conn) == Routes.auth_path(conn, :login_form)
assert redirected_to(conn) ==
Routes.auth_path(conn, :login_form,
return_to: "/some-other-public-site.io/verification"
)
end
test "public site - no stats with skip_to_dashboard", %{

View File

@ -149,6 +149,7 @@ defmodule PlausibleWeb.EmailTest do
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])
suggested_plan = %Plausible.Billing.Plan{volume: "100k"}
@ -159,7 +160,7 @@ defmodule PlausibleWeb.EmailTest do
}
%{html_body: html_body, subject: subject} =
PlausibleWeb.Email.over_limit_email(user, usage, suggested_plan)
PlausibleWeb.Email.over_limit_email(user, team, usage, suggested_plan)
assert subject == "[Action required] You have outgrown your Plausible subscription tier"
@ -170,18 +171,26 @@ defmodule PlausibleWeb.EmailTest do
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"]|) ==
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"]|) ==
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)
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
@ -191,7 +200,8 @@ defmodule PlausibleWeb.EmailTest do
last_cycle: %{date_range: last_cycle, total: 32_100}
}
%{html_body: html_body} = PlausibleWeb.Email.over_limit_email(user, usage, suggested_plan)
%{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"
@ -201,6 +211,7 @@ defmodule PlausibleWeb.EmailTest do
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])
suggested_plan = %Plausible.Billing.Plan{volume: "100k"}
@ -211,7 +222,7 @@ defmodule PlausibleWeb.EmailTest do
}
%{html_body: html_body, subject: subject} =
PlausibleWeb.Email.dashboard_locked(user, usage, suggested_plan)
PlausibleWeb.Email.dashboard_locked(user, team, usage, suggested_plan)
assert subject == "[Action required] Your Plausible dashboard is now locked"
@ -222,18 +233,26 @@ defmodule PlausibleWeb.EmailTest do
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"]|) ==
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"]|) ==
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)
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
@ -243,7 +262,8 @@ defmodule PlausibleWeb.EmailTest do
last_cycle: %{date_range: last_cycle, total: 32_100}
}
%{html_body: html_body} = PlausibleWeb.Email.dashboard_locked(user, usage, suggested_plan)
%{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"
@ -281,13 +301,23 @@ defmodule PlausibleWeb.EmailTest do
describe "approaching accept_traffic_until" do
test "renders first warning" do
user = build(:user, name: "John Doe")
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(user)
PlausibleWeb.Email.approaching_accept_traffic_until(notification)
assert subject == "We'll stop counting your stats"
assert body =~ plausible_link()
assert body =~ plausible_link(team: team, label: "login to your Plausible account")
assert body =~ "Hey John,"
assert body =~
@ -295,13 +325,23 @@ defmodule PlausibleWeb.EmailTest do
end
test "renders final warning" do
user = build(:user)
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(user)
PlausibleWeb.Email.approaching_accept_traffic_until_tomorrow(notification)
assert subject == "A reminder that we'll stop counting your stats tomorrow"
assert body =~ plausible_link()
assert body =~ plausible_link(team: team, label: "login to your Plausible account")
assert body =~
"We've noticed that you're still sending us stats so we're writing to inform you that we'll stop accepting stats from your sites tomorrow."
@ -315,7 +355,7 @@ defmodule PlausibleWeb.EmailTest do
emails = [
PlausibleWeb.Email.create_site_email(trial_user),
PlausibleWeb.Email.site_setup_help(trial_user, site),
PlausibleWeb.Email.site_setup_help(trial_user, site.team, site),
PlausibleWeb.Email.site_setup_success(trial_user, site.team, site)
]
@ -426,8 +466,23 @@ defmodule PlausibleWeb.EmailTest do
PlausibleWeb.EmailView.plausible_url()
end
def plausible_link() do
def plausible_link(opts \\ []) do
suffix =
if team = Keyword.get(opts, :team) do
"?__team=#{team.identifier}"
else
""
end
plausible_url = plausible_url()
"<a href=\"#{plausible_url}\">#{plausible_url}</a>"
label =
if label = Keyword.get(opts, :label) do
label
else
plausible_url
end
"<a href=\"#{plausible_url <> suffix}\">#{label}</a>"
end
end

View File

@ -148,7 +148,11 @@ defmodule Plausible.Workers.AcceptTrafficUntilTest do
html_body: ~r/Hey Jane,/,
to: [nil: email],
subject:
PlausibleWeb.Email.approaching_accept_traffic_until(%{name: "", email: email}).subject
PlausibleWeb.Email.approaching_accept_traffic_until(%{
name: "",
email: email,
team: build(:team, identifier: Ecto.UUID.generate())
}).subject
)
end
@ -157,7 +161,11 @@ defmodule Plausible.Workers.AcceptTrafficUntilTest do
html_body: ~r/Hey Jane,/,
to: [nil: email],
subject:
PlausibleWeb.Email.approaching_accept_traffic_until_tomorrow(%{name: "", email: email}).subject
PlausibleWeb.Email.approaching_accept_traffic_until_tomorrow(%{
name: "",
email: email,
team: build(:team, identifier: Ecto.UUID.generate())
}).subject
)
end

View File

@ -55,7 +55,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
perform_job(SendTrialNotifications, %{})
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(user))
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(user, site.team))
end
test "includes billing member in recipients" do
@ -69,8 +69,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
perform_job(SendTrialNotifications, %{})
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(user))
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(billing_member))
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(user, team))
assert_delivered_email(PlausibleWeb.Email.trial_one_week_reminder(billing_member, team))
end
test "sends an upgrade email the day before the trial ends" do
@ -88,7 +88,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
perform_job(SendTrialNotifications, %{})
assert_delivered_email(
PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage, suggested_plan)
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "tomorrow", usage, suggested_plan)
)
end
@ -107,7 +107,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
perform_job(SendTrialNotifications, %{})
assert_delivered_email(
PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
)
end
@ -117,7 +117,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 9_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~
"In the last month, your account has used 9,000 billable pageviews."
@ -129,7 +130,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 9_100, custom_events: 100}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~
"In the last month, your account has used 9,100 billable pageviews and custom events in total."
@ -147,7 +149,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
perform_job(SendTrialNotifications, %{})
assert_delivered_email(PlausibleWeb.Email.trial_over_email(user))
assert_delivered_email(PlausibleWeb.Email.trial_over_email(user, site.team))
end
test "does not send a notification if user has a subscription" do
@ -175,7 +177,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 9_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 10k/mo plan."
end
@ -185,7 +189,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 90_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 100k/mo plan."
end
@ -195,7 +201,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 180_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 200k/mo plan."
end
@ -205,7 +213,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 450_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 500k/mo plan."
end
@ -215,7 +225,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 900_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 1M/mo plan."
end
@ -225,7 +237,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 1_800_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 2M/mo plan."
end
@ -235,7 +249,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 4_500_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 5M/mo plan."
end
@ -245,7 +261,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 9_000_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "we recommend you select a 10M/mo plan."
end
@ -255,7 +273,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
usage = %{total: 20_000_000, custom_events: 0}
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "please reply back to this email to get a quote for your volume"
end
@ -266,7 +286,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
subscribe_to_enterprise_plan(user, paddle_plan_id: "enterprise-plan-id")
suggested_plan = Plausible.Billing.Plans.suggest(site.team, usage.total)
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage, suggested_plan)
email =
PlausibleWeb.Email.trial_upgrade_email(user, site.team, "today", usage, suggested_plan)
assert email.html_body =~ "please reply back to this email to get a quote for your volume"
end
end