Skip to content

Commit 9ddfc53

Browse files
authored
feat: email campaigns (#121)
1 parent ee17668 commit 9ddfc53

File tree

9 files changed

+495
-5
lines changed

9 files changed

+495
-5
lines changed

assets/js/app.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,23 +532,29 @@ const Hooks = {
532532
},
533533
},
534534
LocalStateStore: {
535+
getStorage() {
536+
const storage = this.el.getAttribute("data-storage");
537+
return storage === "localStorage" ? localStorage : sessionStorage;
538+
},
539+
535540
mounted() {
541+
this.storage = this.getStorage();
536542
this.handleEvent("store", (obj) => this.store(obj));
537543
this.handleEvent("clear", (obj) => this.clear(obj));
538544
this.handleEvent("restore", (obj) => this.restore(obj));
539545
},
540546

541547
store(obj) {
542-
sessionStorage.setItem(obj.key, obj.data);
548+
this.storage.setItem(obj.key, obj.data);
543549
},
544550

545551
restore(obj) {
546-
const data = sessionStorage.getItem(obj.key);
552+
const data = this.storage.getItem(obj.key);
547553
this.pushEvent(obj.event, data);
548554
},
549555

550556
clear(obj) {
551-
sessionStorage.removeItem(obj.key);
557+
this.storage.removeItem(obj.key);
552558
},
553559
},
554560
CtrlEnterSubmit: {

config/config.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ config :algora, Oban,
5656
transfers: 1,
5757
activity_notifier: 1,
5858
activity_mailer: 1,
59-
activity_discord: 10
59+
activity_discord: 10,
60+
campaign_emails: 1
6061
]
6162

6263
# Configures the mailer
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
defmodule Algora.Activities.Jobs.SendCampaignEmail do
2+
@moduledoc false
3+
use Oban.Worker,
4+
queue: :campaign_emails,
5+
unique: [period: :infinity, keys: [:id, :recipient_email]],
6+
max_attempts: 1
7+
8+
alias Algora.Workspace
9+
alias AlgoraWeb.Admin.CampaignLive
10+
11+
@impl Oban.Worker
12+
def perform(%Oban.Job{
13+
args: %{
14+
"id" => _id,
15+
"recipient_email" => _recipient_email,
16+
"subject" => subject,
17+
"recipient" => encoded_recipient,
18+
"template_params" => encoded_template_params
19+
}
20+
}) do
21+
recipient = Algora.Util.base64_to_term!(encoded_recipient)
22+
template_params = Algora.Util.base64_to_term!(encoded_template_params)
23+
24+
token = Algora.Admin.token!()
25+
26+
with {:ok, repo} <- Workspace.ensure_repository(token, recipient["repo_owner"], recipient["repo_name"]),
27+
{:ok, _owner} <- Workspace.ensure_user(token, recipient["repo_owner"]),
28+
{:ok, _contributors} <- Workspace.ensure_contributors(token, repo),
29+
{:ok, _languages} <- Workspace.ensure_repo_tech_stack(token, repo) do
30+
CampaignLive.deliver_email(recipient, subject, template_params)
31+
end
32+
end
33+
end

lib/algora/mailer.ex

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,120 @@
11
defmodule Algora.Mailer do
22
@moduledoc false
33
use Swoosh.Mailer, otp_app: :algora
4+
5+
require Logger
6+
7+
def deliver_with_logging(mail) do
8+
case deliver(mail) do
9+
{:ok, _} ->
10+
{:ok, mail}
11+
12+
{:error, reason} ->
13+
Logger.error("""
14+
Email delivery failed:
15+
16+
Subject: #{mail.subject}
17+
To: #{inspect(mail.to)}
18+
Reason: #{inspect(reason, pretty: true)}
19+
""")
20+
21+
{:error, reason}
22+
end
23+
end
24+
25+
def html_template(template_params) do
26+
"""
27+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
28+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
29+
<head>
30+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
31+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
32+
<!--[if gte mso 9]><xml>
33+
<o:OfficeDocumentSettings>
34+
<o:AllowPNG/>
35+
<o:PixelsPerInch>96</o:PixelsPerInch>
36+
</o:OfficeDocumentSettings>
37+
</xml><![endif]-->
38+
</head>
39+
<body style="margin: 0; padding: 0; min-width: 100%; background-color: #ffffff;">
40+
<div style="background-color: #ffffff; box-sizing: border-box; display: block; padding: 0;">
41+
<table cellpadding="0" cellspacing="0" width="100%">
42+
<tr>
43+
<td style="font-family:sans-serif; font-size: 16px;">
44+
#{html_sections(template_params)}
45+
</td>
46+
</tr>
47+
</table>
48+
</div>
49+
</body>
50+
</html>
51+
"""
52+
end
53+
54+
def text_template(template_params) do
55+
"""
56+
==============================
57+
58+
#{text_sections(template_params)}
59+
60+
==============================
61+
"""
62+
end
63+
64+
defp text_sections(template_params) do
65+
template_params
66+
|> Enum.map(fn {type, value} -> text_section(type, value) end)
67+
|> Enum.intersperse("\n\n")
68+
end
69+
70+
defp text_section(:cta, %{href: href, src: src}) do
71+
~s|#{href}\n\n#{src}|
72+
end
73+
74+
defp text_section(_, value) do
75+
value
76+
end
77+
78+
defp html_sections(template_params) do
79+
for {type, value} <- template_params,
80+
do: html_section(type, value)
81+
end
82+
83+
defp html_section(:markdown, value) do
84+
html = Cmark.to_html(value)
85+
86+
~s"""
87+
<table cellpadding="0" cellspacing="0" width="100%">
88+
<tr>
89+
<td>
90+
#{html}
91+
</td>
92+
</tr>
93+
</table>
94+
"""
95+
end
96+
97+
defp html_section(type, value) do
98+
~s|<p style="font-family: sans-serif; font-size: 16px; line-height: 1.5; padding-top: 0;">| <>
99+
html_section_by_type(type, value) <> ~s|</p>|
100+
end
101+
102+
defp html_section_by_type(:cta, %{href: href, src: src}) do
103+
~s|<a href="#{href}" style="word-break: break-all; word-wrap: break-word;">| <>
104+
~s|<img src="#{src}" style="width: 100%; height: auto;">| <>
105+
~s|</a>|
106+
end
107+
108+
defp html_section_by_type(:url, value) do
109+
~s|<a href="#{value}" style="word-break: break-all; word-wrap: break-word;">| <>
110+
value <> ~s|</a>|
111+
end
112+
113+
defp html_section_by_type(:img, value) do
114+
~s|<img src="#{value}" style="width: 100%; height: auto;">|
115+
end
116+
117+
defp html_section_by_type(_, text) do
118+
text
119+
end
4120
end

0 commit comments

Comments
 (0)