Skip to content

Commit abfaed8

Browse files
committed
feat: handle dynamic inline image attachments in email campaign preview and delivery
1 parent 9021c60 commit abfaed8

File tree

2 files changed

+68
-52
lines changed

2 files changed

+68
-52
lines changed

lib/algora/activities/jobs/send_campaign_email.ex

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,50 @@ defmodule Algora.Activities.Jobs.SendCampaignEmail do
99
alias AlgoraWeb.Admin.CampaignLive
1010

1111
@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-
"from_name" => from_name,
20-
"from_email" => from_email,
21-
"preheader" => preheader
22-
}
23-
}) do
24-
recipient = Algora.Util.base64_to_term!(encoded_recipient)
25-
template_params = Algora.Util.base64_to_term!(encoded_template_params)
12+
def perform(%Oban.Job{args: %{"recipient" => encoded_recipient} = args}) do
13+
args
14+
|> Map.put("recipient", Algora.Util.base64_to_term!(encoded_recipient))
15+
|> deliver_email()
16+
end
17+
18+
defp deliver_email(%{
19+
"subject" => subject,
20+
"recipient" => %{"repo_owner" => repo_owner, "repo_name" => repo_name} = recipient,
21+
"template" => template,
22+
"from_name" => from_name,
23+
"from_email" => from_email,
24+
"preheader" => preheader
25+
}) do
2626
token = Algora.Admin.token!()
2727

28-
with {:ok, repo} <- Workspace.ensure_repository(token, recipient["repo_owner"], recipient["repo_name"]),
29-
{:ok, _owner} <- Workspace.ensure_user(token, recipient["repo_owner"]),
28+
with {:ok, repo} <- Workspace.ensure_repository(token, repo_owner, repo_name),
29+
{:ok, _owner} <- Workspace.ensure_user(token, repo_owner),
3030
{:ok, _contributors} <- Workspace.ensure_contributors(token, repo),
3131
{:ok, _languages} <- Workspace.ensure_repo_tech_stack(token, repo) do
3232
CampaignLive.deliver_email(
3333
recipient: recipient,
3434
subject: subject,
35-
template_params: template_params,
35+
template: template,
3636
from: {from_name, from_email},
3737
preheader: preheader
3838
)
3939
end
4040
end
41+
42+
defp deliver_email(%{
43+
"subject" => subject,
44+
"recipient" => recipient,
45+
"template" => template,
46+
"from_name" => from_name,
47+
"from_email" => from_email,
48+
"preheader" => preheader
49+
}) do
50+
CampaignLive.deliver_email(
51+
recipient: recipient,
52+
subject: subject,
53+
template: template,
54+
from: {from_name, from_email},
55+
preheader: preheader
56+
)
57+
end
4158
end

lib/algora_web/live/admin/campaign_live.ex

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule AlgoraWeb.Admin.CampaignLive do
77
import Ecto.Query
88

99
alias Algora.Activities.Jobs.SendCampaignEmail
10+
alias Algora.Admin
1011
alias Algora.Mailer
1112
alias Algora.Repo
1213
alias Algora.Workspace.Repository
@@ -302,20 +303,12 @@ defmodule AlgoraWeb.Admin.CampaignLive do
302303
Repo.transact(fn _ ->
303304
recipients
304305
|> Enum.map(fn recipient ->
305-
template_params = [
306-
markdown: render_preview(template, recipient),
307-
cta: %{
308-
href: "#{AlgoraWeb.Endpoint.url()}/go/#{recipient["repo_owner"]}/#{recipient["repo_name"]}",
309-
src: "cid:#{recipient["repo_owner"]}.png"
310-
}
311-
]
312-
313306
%{
314307
id: "2025-04-oss",
315308
subject: subject,
316309
recipient_email: recipient["email"],
317310
recipient: Algora.Util.term_to_base64(recipient),
318-
template_params: Algora.Util.term_to_base64(template_params),
311+
template: template,
319312
from_name: from_name,
320313
from_email: from_email,
321314
preheader: render_preview(preheader, recipient)
@@ -332,38 +325,44 @@ defmodule AlgoraWeb.Admin.CampaignLive do
332325
end
333326

334327
def deliver_email(opts) do
335-
recipient = opts[:recipient]
336-
337-
case :get
338-
|> Finch.build("https://algora.io/og/go/#{recipient["repo_owner"]}/#{recipient["repo_name"]}")
339-
|> Finch.request(Algora.Finch) do
340-
{:ok, %Finch.Response{status: status, body: body}} when status in 200..299 ->
341-
opts
342-
|> Keyword.put(:attachments, [
343-
Swoosh.Attachment.new({:data, body},
344-
filename: "#{recipient["repo_owner"]}.png",
345-
content_type: "image/png",
346-
type: :inline
347-
)
348-
])
349-
|> deliver()
328+
case opts[:template] |> render_preview(opts[:recipient]) |> extract_attachments() do
329+
{:ok, {preview, attachments}} ->
330+
Email.new()
331+
|> Email.to(opts[:recipient]["email"])
332+
|> Email.from(opts[:from])
333+
|> Email.subject(opts[:subject])
334+
|> Email.text_body(Mailer.text_template(markdown: preview))
335+
|> Email.html_body(Mailer.html_template([markdown: preview], preheader: opts[:preheader]))
336+
|> then(&Enum.reduce(attachments, &1, fn attachment, acc -> Email.attachment(acc, attachment) end))
337+
|> Mailer.deliver_with_logging()
350338

351339
{:error, reason} ->
352-
raise reason
340+
Admin.alert("Failed to deliver email: #{inspect(reason)}")
341+
{:error, reason}
353342
end
354343
end
355344

356-
defp deliver(opts) do
357-
email =
358-
Email.new()
359-
|> Email.to(opts[:recipient]["email"])
360-
|> Email.from(opts[:from])
361-
|> Email.subject(opts[:subject])
362-
|> Email.text_body(Mailer.text_template(opts[:template_params]))
363-
|> Email.html_body(Mailer.html_template(opts[:template_params], preheader: opts[:preheader]))
345+
defp extract_attachments(preview) do
346+
image_regex = ~r/!\[(.*?)\]\((.*?)\)/
347+
348+
image_regex
349+
|> Regex.scan(preview)
350+
|> Enum.reduce_while({:ok, {preview, []}}, fn [full_match, alt, src], {:ok, {current_preview, current_attachments}} ->
351+
case :get |> Finch.build(src) |> Finch.request(Algora.Finch) do
352+
{:ok, %Finch.Response{status: status, body: body}} when status in 200..299 ->
353+
attachment =
354+
Swoosh.Attachment.new({:data, body}, filename: "#{alt}.png", content_type: "image/png", type: :inline)
364355

365-
email = Enum.reduce(opts[:attachments], email, fn attachment, acc -> Email.attachment(acc, attachment) end)
356+
new_preview = String.replace(current_preview, full_match, "![#{alt}](cid:#{alt}.png)")
357+
{:cont, {:ok, {new_preview, [attachment | current_attachments]}}}
366358

367-
Mailer.deliver_with_logging(email)
359+
{:error, reason} ->
360+
{:halt, {:error, reason}}
361+
end
362+
end)
363+
|> case do
364+
{:ok, {preview, attachments}} -> {:ok, {preview, Enum.reverse(attachments)}}
365+
{:error, reason} -> {:error, reason}
366+
end
368367
end
369368
end

0 commit comments

Comments
 (0)