aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/app.ex2
-rw-r--r--lib/mix/tasks/pleroma/user.ex29
-rw-r--r--lib/pleroma/emails/user_email.ex89
-rw-r--r--lib/pleroma/migration_helper/notification_backfill.ex11
-rw-r--r--lib/pleroma/notification.ex47
-rw-r--r--lib/pleroma/user.ex22
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex28
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex3
-rw-r--r--lib/pleroma/web/templates/email/digest.html.eex2
-rw-r--r--lib/pleroma/web/templates/email/mentions.html.eex439
-rw-r--r--lib/pleroma/workers/cron/email_mentions_worker.ex67
12 files changed, 696 insertions, 44 deletions
diff --git a/lib/mix/tasks/pleroma/app.ex b/lib/mix/tasks/pleroma/app.ex
index 0bf7ffabc..aa5c9cd54 100644
--- a/lib/mix/tasks/pleroma/app.ex
+++ b/lib/mix/tasks/pleroma/app.ex
@@ -21,7 +21,7 @@ defmodule Mix.Tasks.Pleroma.App do
scopes =
if opts[:scopes] do
- String.split(opts[:scopes], ",")
+ String.split(opts[:scopes], ",", trim: true)
else
["read", "write", "follow", "push"]
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 53d5fc6d9..d2a545e91 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -433,6 +433,35 @@ defmodule Mix.Tasks.Pleroma.User do
|> Stream.run()
end
+ def run(["email_notifications", nickname | options]) do
+ start_pleroma()
+
+ {opts, _} =
+ OptionParser.parse!(options,
+ strict: [digest: :boolean, notifications: :string],
+ aliases: [n: :notifications]
+ )
+
+ params =
+ Map.new(opts, fn
+ {:digest, v} ->
+ {"digest", v}
+
+ {:notifications, v} ->
+ types = if v == "off", do: [], else: String.split(v, ",", trim: true)
+ {"notifications", types}
+ end)
+
+ with keys when keys != [] <- Map.keys(params),
+ %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ {:ok, user} = User.update_email_notifications(user, params)
+ shell_info("Email notifications for user #{user.nickname} were successfully updated.")
+ else
+ [] -> shell_error("No changes passed")
+ _ -> shell_error("No local user #{nickname}")
+ end
+ end
+
defp set_moderator(user, value) do
{:ok, user} =
user
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 52f3d419d..73cea4962 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Emails.UserEmail do
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
alias Pleroma.Config
+ alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@@ -120,6 +121,27 @@ defmodule Pleroma.Emails.UserEmail do
|> html_body(html_body)
end
+ defp prepare_mention(%Notification{type: type} = notification, acc)
+ when type in ["mention", "pleroma:chat_mention"] do
+ object = Pleroma.Object.normalize(notification.activity, fetch: false)
+
+ if object do
+ object = update_in(object.data["content"], &format_links/1)
+
+ mention = %{
+ data: notification,
+ object: object,
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+
+ [mention | acc]
+ else
+ acc
+ end
+ end
+
+ defp prepare_mention(_, acc), do: acc
+
@doc """
Email used in digest email notifications
Includes Mentions and New Followers data
@@ -127,25 +149,12 @@ defmodule Pleroma.Emails.UserEmail do
"""
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
def digest_email(user) do
- notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
+ notifications = Notification.for_user_since(user, user.last_digest_emailed_at)
mentions =
notifications
|> Enum.filter(&(&1.activity.data["type"] == "Create"))
- |> Enum.map(fn notification ->
- object = Pleroma.Object.normalize(notification.activity, fetch: false)
-
- if not is_nil(object) do
- object = update_in(object.data["content"], &format_links/1)
-
- %{
- data: notification,
- object: object,
- from: User.get_by_ap_id(notification.activity.actor)
- }
- end
- end)
- |> Enum.filter(& &1)
+ |> Enum.reduce([], &prepare_mention/2)
followers =
notifications
@@ -165,7 +174,6 @@ defmodule Pleroma.Emails.UserEmail do
unless Enum.empty?(mentions) do
styling = Config.get([__MODULE__, :styling])
- logo = Config.get([__MODULE__, :logo])
html_data = %{
instance: instance_name(),
@@ -176,20 +184,15 @@ defmodule Pleroma.Emails.UserEmail do
styling: styling
}
- logo_path =
- if is_nil(logo) do
- Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
- else
- Path.join(Config.get([:instance, :static_dir]), logo)
- end
+ {logo_path, logo} = logo_path()
new()
|> to(recipient(user))
|> from(sender())
|> subject("Your digest from #{instance_name()}")
|> put_layout(false)
- |> render_body("digest.html", html_data)
- |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
+ |> render_body("digest.html", Map.put(html_data, :logo, logo))
+ |> attachment(Swoosh.Attachment.new(logo_path, filename: logo, type: :inline))
end
end
@@ -242,4 +245,42 @@ defmodule Pleroma.Emails.UserEmail do
|> subject("Your account archive is ready")
|> html_body(html_body)
end
+
+ @spec mentions_notification_email(User.t(), [Notification.t()]) :: Swoosh.Email.t()
+ def mentions_notification_email(user, mentions) do
+ html_data = %{
+ instance: instance_name(),
+ user: user,
+ mentions: Enum.reduce(mentions, [], &prepare_mention/2),
+ unsubscribe_link: unsubscribe_url(user, "mentions_email"),
+ styling: Config.get([__MODULE__, :styling])
+ }
+
+ now = NaiveDateTime.utc_now()
+
+ {logo_path, logo} = logo_path()
+
+ new()
+ |> to(recipient(user))
+ |> from(sender())
+ |> subject(
+ "[Pleroma] New mentions from #{instance_name()} for #{
+ Timex.format!(now, "{Mfull} {D}, {YYYY} at {h12}:{m} {AM}")
+ }"
+ )
+ |> put_layout(false)
+ |> render_body("mentions.html", Map.put(html_data, :logo, logo))
+ |> attachment(Swoosh.Attachment.new(logo_path, filename: logo, type: :inline))
+ end
+
+ defp logo_path do
+ logo_path =
+ if logo = Config.get([__MODULE__, :logo]) do
+ Path.join(Config.get([:instance, :static_dir]), logo)
+ else
+ Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
+ end
+
+ {logo_path, Path.basename(logo_path)}
+ end
end
diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex
index 62b710f82..e634ba326 100644
--- a/lib/pleroma/migration_helper/notification_backfill.ex
+++ b/lib/pleroma/migration_helper/notification_backfill.ex
@@ -11,9 +11,11 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
def fill_in_notification_types do
query =
- from(n in Pleroma.Notification,
+ from(n in "notifications",
where: is_nil(n.type),
- preload: :activity
+ join: a in "activities",
+ on: n.activity_id == a.id,
+ select: %{id: n.id, activity: %{id: a.id, data: a.data}}
)
query
@@ -22,9 +24,8 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
if notification.activity do
type = type_from_activity(notification.activity)
- notification
- |> Ecto.Changeset.change(%{type: type})
- |> Repo.update()
+ from(n in "notifications", where: n.id == ^notification.id)
+ |> Repo.update_all(set: [type: type])
end
end)
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 7efbdc49a..8adbc07f4 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -37,6 +37,7 @@ defmodule Pleroma.Notification do
field(:type, :string)
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
+ field(:notified_at, :naive_datetime)
timestamps()
end
@@ -249,7 +250,7 @@ defmodule Pleroma.Notification do
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
[]
"""
- @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
+ @spec for_user_since(User.t(), NaiveDateTime.t()) :: [t()]
def for_user_since(user, date) do
from(n in for_user_query(user),
where: n.updated_at > ^date
@@ -664,4 +665,48 @@ defmodule Pleroma.Notification do
)
|> Repo.update_all(set: [seen: true])
end
+
+ defp unread_mentions_in_timeframe_query(query \\ __MODULE__, args) do
+ types = args[:types] || ["mention", "pleroma:chat_mention"]
+ max_at = args[:max_at]
+
+ from(n in query,
+ where: n.seen == false,
+ where: is_nil(n.notified_at),
+ where: n.type in ^types,
+ where: n.inserted_at <= ^max_at
+ )
+ end
+
+ @spec users_ids_with_unread_mentions(NaiveDateTime.t()) :: [String.t()]
+ def users_ids_with_unread_mentions(max_at) do
+ from(n in unread_mentions_in_timeframe_query(%{max_at: max_at}),
+ join: u in assoc(n, :user),
+ where: not is_nil(u.email),
+ distinct: n.user_id,
+ select: n.user_id
+ )
+ |> Repo.all()
+ end
+
+ @spec for_user_unread_mentions(User.t(), NaiveDateTime.t()) :: [t()]
+ def for_user_unread_mentions(%User{} = user, max_at) do
+ args = %{
+ max_at: max_at,
+ types: user.email_notifications["notifications"]
+ }
+
+ user
+ |> for_user_query()
+ |> unread_mentions_in_timeframe_query(args)
+ |> Repo.all()
+ end
+
+ @spec update_notified_at([pos_integer()]) :: {non_neg_integer(), nil}
+ def update_notified_at(ids \\ []) do
+ from(n in __MODULE__,
+ where: n.id in ^ids
+ )
+ |> Repo.update_all(set: [notified_at: NaiveDateTime.utc_now()])
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index b78777141..c5ea1ba10 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -131,7 +131,11 @@ defmodule Pleroma.User do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
- field(:email_notifications, :map, default: %{"digest" => false})
+
+ field(:email_notifications, :map,
+ default: %{"digest" => false, "notifications" => ["mention", "pleroma:chat_mention"]}
+ )
+
field(:mascot, :map, default: nil)
field(:emoji, :map, default: %{})
field(:pleroma_settings_store, :map, default: %{})
@@ -525,7 +529,8 @@ defmodule Pleroma.User do
:is_discoverable,
:actor_type,
:accepts_chat_messages,
- :disclose_client
+ :disclose_client,
+ :email_notifications
]
)
|> unique_constraint(:nickname)
@@ -2390,17 +2395,14 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
+ @spec update_email_notifications(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def update_email_notifications(user, settings) do
- email_notifications =
- user.email_notifications
- |> Map.merge(settings)
- |> Map.take(["digest"])
+ email_notifications = Map.merge(user.email_notifications, settings)
- params = %{email_notifications: email_notifications}
fields = [:email_notifications]
user
- |> cast(params, fields)
+ |> cast(%{email_notifications: email_notifications}, fields)
|> validate_required(fields)
|> update_and_set_cache()
end
@@ -2431,8 +2433,8 @@ defmodule Pleroma.User do
end
end
- @spec add_to_block(User.t(), User.t()) ::
- {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
+ @spec remove_from_block(User.t(), User.t()) ::
+ {:ok, UserRelationship.t() | nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 54e5ebc76..d0d7d751e 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -635,7 +635,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
},
- actor_type: ActorType
+ actor_type: ActorType,
+ email_notifications: email_notifications()
},
example: %{
bot: false,
@@ -760,6 +761,31 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ defp email_notifications do
+ %Schema{
+ title: "EmailNotificationsObject",
+ description: "User Email notification settings",
+ type: :object,
+ properties: %{
+ digest: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Whether the account receives digest email"
+ },
+ notifications: %Schema{
+ type: :array,
+ nullable: true,
+ description: "List of notification types to receive by Email",
+ items: %Schema{type: :string}
+ }
+ },
+ example: %{
+ "digest" => true,
+ "notifications" => ["mention", "pleroma:chat_mention"]
+ }
+ }
+ end
+
defp array_of_lists do
%Schema{
title: "ArrayOfLists",
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 7a1e99044..a8f11786b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -213,6 +213,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_locked, params[:locked])
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
+ |> Maps.put_if_present(:email_notifications, params[:email_notifications])
# What happens here:
#
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index ac25aefdd..4165aa890 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -279,7 +279,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages,
- favicon: favicon
+ favicon: favicon,
+ email_notifications: user.email_notifications
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex
index 60eceff22..9afb2ca49 100644
--- a/lib/pleroma/web/templates/email/digest.html.eex
+++ b/lib/pleroma/web/templates/email/digest.html.eex
@@ -126,7 +126,7 @@
<div align="center" class="img-container center"
style="padding-right: 0px;padding-left: 0px;">
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
- align="center" alt="Image" border="0" class="center" src="cid:logo.svg"
+ align="center" alt="Image" border="0" class="center" src="cid:<%= @logo %>"
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
title="Image" height="80" />
<!--[if mso]></td></tr></table><![endif]-->
diff --git a/lib/pleroma/web/templates/email/mentions.html.eex b/lib/pleroma/web/templates/email/mentions.html.eex
new file mode 100644
index 000000000..c95dc97f1
--- /dev/null
+++ b/lib/pleroma/web/templates/email/mentions.html.eex
@@ -0,0 +1,439 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"
+ xmlns:v="urn:schemas-microsoft-com:vml">
+
+<head>
+ <!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+ <meta content="width=device-width" name="viewport" />
+ <!--[if !mso]><!-->
+ <meta content="IE=edge" http-equiv="X-UA-Compatible" />
+ <!--<![endif]-->
+ <title><%= @email.subject %><</title>
+ <!--[if !mso]><!-->
+ <!--<![endif]-->
+ <style type="text/css">
+ body {
+ margin: 0;
+ padding: 0;
+ }
+
+ a {
+
+ color: <%= @styling.link_color %>;
+ text-decoration: none;
+ }
+
+ table,
+ td,
+ tr {
+ vertical-align: top;
+ border-collapse: collapse;
+ }
+
+ * {
+ line-height: inherit;
+ }
+
+ a[x-apple-data-detectors=true] {
+ color: inherit !important;
+ text-decoration: none !important;
+ }
+ </style>
+ <style id="media-query" type="text/css">
+ @media (max-width: 610px) {
+
+ .block-grid,
+ .col {
+ min-width: 320px !important;
+ max-width: 100% !important;
+ display: block !important;
+ }
+
+ .block-grid {
+ width: 100% !important;
+ }
+
+ .col {
+ width: 100% !important;
+ }
+
+ .col>div {
+ margin: 0 auto;
+ }
+
+ .no-stack .col {
+ min-width: 0 !important;
+ display: table-cell !important;
+ }
+
+ .no-stack.two-up .col {
+ width: 50% !important;
+ }
+
+ .no-stack .col.num4 {
+ width: 33% !important;
+ }
+
+ .no-stack .col.num8 {
+ width: 66% !important;
+ }
+
+ .no-stack .col.num4 {
+ width: 33% !important;
+ }
+
+ .no-stack .col.num3 {
+ width: 25% !important;
+ }
+
+ .no-stack .col.num6 {
+ width: 50% !important;
+ }
+
+ .no-stack .col.num9 {
+ width: 75% !important;
+ }
+
+ }
+ </style>
+</head>
+
+<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;">
+ <!--[if IE]><div class="ie-browser"><![endif]-->
+ <table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation"
+ style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td style="word-break: break-word; vertical-align: top;" valign="top">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]-->
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <div align="center" class="img-container center"
+ style="padding-right: 0px;padding-left: 0px;">
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
+ align="center" alt="Image" border="0" class="center" src="cid:<%= @logo %>"
+ style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
+ title="Image" height="80" />
+ <!--[if mso]></td></tr></table><![endif]-->
+ </div>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
+ <p style="line-height: 36px; text-align: center; margin: 0;"><span
+ style="font-size: 30px; color: <%= @styling.header_color %>;">Hey <%= @user.nickname %>, here is what you've missed!</span></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
+ <!--<![endif]-->
+ <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td class="divider_inner"
+ style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
+ valign="top">
+ <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
+ height="0" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td height="0"
+ style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top"><span></span></td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <p
+ style="font-size: 12px; line-height: 24px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
+ <span style="font-size: 20px;">Mentions</span></p>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+
+ <%= for %{data: mention, object: object, from: from} <- @mentions do %>
+ <%# mention START %>
+ <%# user card START %>
+ <div style="background-color:transparent;">
+ <div class="block-grid mixed-two-up no-stack"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num3"
+ style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;">
+ <!--<![endif]-->
+ <div align="left" class="img-container left "
+ style="padding-right: 0px;padding-left: 0px;">
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img
+ alt="<%= from.name %>" border="0" class="left " src="<%= avatar_url(from) %>"
+ style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;"
+ title="<%= from.name %>" width="76" />
+ <!--[if mso]></td></tr></table><![endif]-->
+ </div>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num9"
+ style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
+ <p style="font-size: 14px; line-height: 19px; margin: 0;"><span
+ style="font-size: 16px; color: <%= @styling.text_color %>;"><%= from.name %></span></p>
+ <p style="font-size: 14px; line-height: 19px; margin: 0;"><span
+ style="font-size: 16px;"><%= link "@" <> from.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: mention.activity.actor %></span></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <%# user card END %>
+
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
+ <span style="font-size: 16px; line-height: 19px;"><%= raw object.data["content"] %></span></div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;">
+ <p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date object.data["published"] %></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <%# mention END %>
+ <% end %>
+
+ <%# divider start %>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td class="divider_inner"
+ style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
+ valign="top">
+ <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
+ height="0" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td height="0"
+ style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top"><span></span></td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+
+ <%# divider end %>
+
+
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <p
+ style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
+ <span style="font-size: 14px;">You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</span></p>
+ <p
+ style="font-size: 12px; line-height: 14px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
+  </p>
+ <p
+ style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
+ <span style="font-size: 14px;">The email address you are subscribed as is <a href="mailto:<%= @user.email %>" style="color: <%= @styling.link_color %>;text-decoration: none;"><%= @user.email %></a>. </span></p>
+ <p
+ style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
+ <span style="font-size: 14px;">To unsubscribe, please go <%= link "here", style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link %>.</span></p>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--[if (IE)]></div><![endif]-->
+</body>
+
+</html>
diff --git a/lib/pleroma/workers/cron/email_mentions_worker.ex b/lib/pleroma/workers/cron/email_mentions_worker.ex
new file mode 100644
index 000000000..791f09c28
--- /dev/null
+++ b/lib/pleroma/workers/cron/email_mentions_worker.ex
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.EmailMentionsWorker do
+ use Pleroma.Workers.WorkerHelper, queue: "email_mentions"
+
+ @impl true
+ def perform(%Job{args: %{"op" => "email_mentions", "user_id" => id}}) do
+ user = Pleroma.User.get_cached_by_id(id)
+
+ timeframe =
+ Pleroma.Config.get([__MODULE__, :timeframe], 30)
+ |> :timer.minutes()
+
+ max_inserted_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(-timeframe, :millisecond)
+ |> NaiveDateTime.truncate(:second)
+
+ mentions = Pleroma.Notification.for_user_unread_mentions(user, max_inserted_at)
+
+ if mentions != [] do
+ user
+ |> Pleroma.Emails.UserEmail.mentions_notification_email(mentions)
+ |> Pleroma.Emails.Mailer.deliver()
+ |> case do
+ {:ok, _} ->
+ Enum.map(mentions, & &1.id)
+
+ _ ->
+ []
+ end
+ |> Pleroma.Notification.update_notified_at()
+ end
+
+ :ok
+ end
+
+ @impl true
+ def perform(_) do
+ config = Pleroma.Config.get(__MODULE__, [])
+
+ if Keyword.get(config, :enabled, false) do
+ timeframe = Keyword.get(config, :timeframe, 30)
+ period = timeframe * 60
+
+ max_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(-:timer.minutes(timeframe), :millisecond)
+ |> NaiveDateTime.truncate(:second)
+
+ Pleroma.Notification.users_ids_with_unread_mentions(max_at)
+ |> Enum.each(&insert_job(&1, unique: [period: period]))
+ end
+
+ :ok
+ end
+
+ defp insert_job(user_id, args) do
+ Pleroma.Workers.Cron.EmailMentionsWorker.enqueue(
+ "email_mentions",
+ %{"user_id" => user_id},
+ args
+ )
+ end
+end