diff --git a/lib/chat_api/emails.ex b/lib/chat_api/emails.ex index bb7276f63..fe1d92cea 100644 --- a/lib/chat_api/emails.ex +++ b/lib/chat_api/emails.ex @@ -3,124 +3,247 @@ defmodule ChatApi.Emails do require Logger - alias ChatApi.{Accounts, Conversations, Repo, Users} + alias ChatApi.{ + Accounts, + Conversations, + Mailbox, + Repo, + Users + } + + alias ChatApi.Customers.Customer alias ChatApi.Emails.Email alias ChatApi.Messages.Message alias ChatApi.Users.User alias ChatApi.Accounts.Account - @type deliver_result() :: {:ok, term()} | {:error, binary()} | {:warning, binary()} + @from_address System.get_env("FROM_ADDRESS") || "noreply@mail.heypapercups.io" - @spec send_ad_hoc_email(keyword()) :: deliver_result() + @spec send_ad_hoc_email(keyword()) :: {:error, any()} | {:ok, Tesla.Env.t()} def send_ad_hoc_email(to: to, from: from, subject: subject, text: text, html: html) do - Email.generic( + Mailbox.send_email(%Mailbox.Email{ to: to, from: from, subject: subject, - text: text, - html: html - ) - |> deliver() + text_body: text, + html_body: html + }) end - @spec send_new_message_alerts(Message.t()) :: [deliver_result()] + @spec send_new_message_alerts(Message.t()) :: [{:error, any()} | {:ok, Tesla.Env.t()}] def send_new_message_alerts(%Message{} = message) do message |> get_users_to_email() - |> Enum.map(fn email -> - email |> Email.new_message_alert(message) |> deliver() - end) + |> Enum.map(fn email -> send_new_message_alert(email, message) end) end - @spec send_welcome_email(binary()) :: deliver_result() - def send_welcome_email(address) do - address |> Email.welcome() |> deliver() + # TODO: Add some recent messages for context, rather than just a single message + # (See the `conversation_reply` method for an example of this) + @spec send_new_message_alert(binary(), Message.t()) :: {:error, any} | {:ok, Tesla.Env.t()} + def send_new_message_alert(email, %Message{ + body: body, + conversation_id: conversation_id, + customer_id: customer_id + }) do + customer = format_customer_name(customer_id) + dashboard_url = "#{app_domain()}/conversations/#{conversation_id}" + + Mailbox.send_email(%Mailbox.Email{ + to: email, + from: "alex@papercups.io", + subject: "#{customer} has sent you a message", + template: :new_message_alert, + data: %{ + sender: customer, + content: body, + dashboard_url: dashboard_url + } + }) end - @spec send_password_reset_email(User.t()) :: deliver_result() - def send_password_reset_email(user) do - user |> Email.password_reset() |> deliver() + @spec send_welcome_email(binary()) :: {:error, any} | {:ok, Tesla.Env.t()} + def send_welcome_email(address) do + Mailbox.send_email(%Mailbox.Email{ + to: address, + from: "alex@papercups.io", + subject: "Welcome to Papercups!", + template: :welcome + }) end - @spec format_sender_name(User.t() | binary(), Account.t() | binary()) :: binary() - def format_sender_name(%User{} = user, %Account{} = account) do - case user.profile do - %{display_name: display_name} when not is_nil(display_name) -> display_name - %{full_name: full_name} when not is_nil(full_name) -> full_name - _ -> "#{account.company_name} Team" - end + @spec send_password_reset_email(User.t()) :: {:error, any} | {:ok, Tesla.Env.t()} + def send_password_reset_email(%User{email: email, password_reset_token: token}) do + Mailbox.send_email(%Mailbox.Email{ + to: email, + from: @from_address, + subject: "Link to reset your Papercups password", + template: :password_reset, + data: %{ + password_reset_url: "#{app_domain()}/reset?token=#{token}" + } + }) end - def format_sender_name(user_id, account_id) - when is_integer(user_id) and is_binary(account_id) do - account = Accounts.get_account!(account_id) + @spec send_user_invitation_email(User.t(), Account.t(), binary(), binary()) :: + {:error, any} | {:ok, Tesla.Env.t()} + def send_user_invitation_email(user, account, to_address, invitation_token) do + company = account.company_name + from_name = format_sender_name(user, account) + from_address = user.email + inviter = if from_name == company, do: from_address, else: "#{from_name} (#{from_address})" - user_id - |> Users.get_user_info() - |> format_sender_name(account) + invitation_url = + "#{app_domain()}/register/#{invitation_token}?#{URI.encode_query(%{email: to_address})}" + + subject = + if from_name == company, + do: "You've been invited to join #{company} on Papercups!", + else: "#{from_name} has invited you to join #{company} on Papercups!" + + Mailbox.send_email(%Mailbox.Email{ + to: to_address, + from: from_address, + subject: subject, + template: :user_invitation, + data: %{ + inviter: inviter, + company: company, + invitation_url: invitation_url + } + }) end - @spec send_conversation_reply_email(keyword()) :: deliver_result() + @spec send_conversation_reply_email(keyword()) :: {:error, any} | {:ok, Tesla.Env.t()} def send_conversation_reply_email( user: user, customer: customer, account: account, messages: messages ) do - Email.conversation_reply( + Mailbox.send_email(%Mailbox.Email{ to: customer.email, - from: format_sender_name(user, account), - reply_to: user.email, - company: account.company_name, - messages: messages, - customer: customer - ) - |> deliver() + from: @from_address, + subject: "New message from #{account.company_name}!", + template: :conversation_reply, + data: %{ + recipient: customer.name, + sender: format_sender_name(user, account), + company: account.company_name, + messages: + Enum.map(messages, fn message -> + %{ + sender: format_message_sender(message, account), + content: message.body + } + end) + }, + # 20 minutes + schedule_in: 20 * 60, + idempotency_period: 20 * 60, + # Ensures uniqueness for these fields within the `idempotency_period` + idempotency_key: + :crypto.hash(:sha256, [ + "conversation_reply", + customer.email, + user.email, + account.id + ]) + |> Base.encode16() + }) end - @spec send_mention_notification_email(keyword()) :: deliver_result() + @spec send_mention_notification_email(keyword()) :: {:error, any} | {:ok, Tesla.Env.t()} def send_mention_notification_email( sender: sender, recipient: recipient, account: account, messages: messages ) do - Email.mention_notification( + conversation_id = messages |> List.first() |> Map.get(:conversation_id) + dashboard_url = "#{app_domain()}/conversations/#{conversation_id}" + + Mailbox.send_email(%Mailbox.Email{ to: recipient.email, - from: format_sender_name(sender, account), + from: @from_address, reply_to: sender.email, - company: account.company_name, - messages: messages, - user: recipient - ) - |> deliver() + subject: "You were mentioned in a message on Papercups!", + template: :mention_notification, + data: %{ + recipient: format_sender_name(recipient), + sender: format_sender_name(sender, account), + dashboard_url: dashboard_url, + messages: + Enum.map(messages, fn message -> + %{ + sender: format_message_sender(message, account), + content: message.body + } + end) + } + }) end - @spec send_user_invitation_email(User.t(), Account.t(), binary(), binary()) :: deliver_result() - def send_user_invitation_email(user, account, to_address, invitation_token) do - Email.user_invitation(%{ - company: account.company_name, - from_address: user.email, - from_name: format_sender_name(user, account), - invitation_token: invitation_token, - to_address: to_address - }) - |> deliver() - end - - @spec send_via_gmail(binary(), map()) :: deliver_result() - def send_via_gmail( - access_token, - %{ - to: _to, - from: _from, - subject: _subject, - text: _text - } = params - ) do - params - |> Email.gmail() - |> deliver(access_token: access_token) + defp format_message_sender(%Message{} = message, %Account{} = account) do + case message do + %{user: %User{} = user, customer_id: nil} -> format_sender_name(user, account) + _ -> "You" + end + end + + @spec format_customer_name(nil | binary() | Customer.t()) :: binary() + def format_customer_name(nil), do: "Anonymous User" + + def format_customer_name(customer_id) when is_binary(customer_id) do + customer_id |> ChatApi.Customers.get_customer!() |> format_customer_name() + end + + def format_customer_name(%Customer{} = customer) do + case customer do + %Customer{email: email, name: name} when is_binary(email) and is_binary(name) -> + "#{name} (#{email})" + + %Customer{email: email} when is_binary(email) -> + email + + %Customer{name: name} when is_binary(name) -> + name + + _ -> + "Anonymous User" + end + end + + @spec format_sender_name(User.t() | binary(), Account.t() | binary()) :: binary() + def format_sender_name(%User{} = user, %Account{} = account) do + case user.profile do + %{display_name: display_name} when not is_nil(display_name) -> display_name + %{full_name: full_name} when not is_nil(full_name) -> full_name + _ -> "#{account.company_name} Team" + end + end + + def format_sender_name(user_id, account_id) + when is_integer(user_id) and is_binary(account_id) do + account = Accounts.get_account!(account_id) + + user_id + |> Users.get_user_info() + |> format_sender_name(account) + end + + @spec format_sender_name(User.t() | binary()) :: binary() | nil + def format_sender_name(%User{} = user) do + case user.profile do + %{display_name: display_name} when not is_nil(display_name) -> display_name + %{full_name: full_name} when not is_nil(full_name) -> full_name + _ -> nil + end + end + + def format_sender_name(user_id) when is_integer(user_id) do + user_id + |> Users.get_user_info() + |> format_sender_name() end @spec get_users_to_email(Message.t()) :: [User.t()] @@ -157,38 +280,10 @@ defmodule ChatApi.Emails do end end - @spec deliver(Email.t()) :: deliver_result() - def deliver(email) do - try do - if has_valid_to_addresses?(email) do - ChatApi.Mailers.deliver(email) - else - {:warning, "Skipped sending to potentially invalid email: #{inspect(email.to)}"} - end - rescue - e -> - IO.puts( - "Email config environment variable may not have been setup properly: #{e.message}" - ) - - {:error, e.message} - end - end - - # TODO: figure out how to clean this up - @spec deliver(Email.t(), keyword()) :: deliver_result() - def deliver(email, access_token: access_token) do - try do - if has_valid_to_addresses?(email) do - ChatApi.Mailers.Gmail.deliver(email, access_token: access_token) - else - {:warning, "Skipped sending to potentially invalid email: #{inspect(email.to)}"} - end - rescue - e -> - IO.puts("Error sending via Gmail: #{e.message}") - - {:error, e.message} + defp app_domain() do + case Application.get_env(:chat_api, :environment) do + :dev -> "http://localhost:3000" + _ -> "https://" <> System.get_env("BACKEND_URL", "app.papercups.io") end end diff --git a/lib/chat_api/emails/email.ex b/lib/chat_api/emails/email.ex index 186e3951f..e09b123dc 100644 --- a/lib/chat_api/emails/email.ex +++ b/lib/chat_api/emails/email.ex @@ -1,27 +1,8 @@ defmodule ChatApi.Emails.Email do import Swoosh.Email - import Ecto.Changeset - - alias ChatApi.Customers.Customer - alias ChatApi.Messages.Message - alias ChatApi.Users.UserProfile @type t :: Swoosh.Email.t() - @from_address System.get_env("FROM_ADDRESS") || "" - @backend_url System.get_env("BACKEND_URL", "app.papercups.io") - - defstruct to_address: nil, message: nil - - def generic(to: to, from: from, subject: subject, text: text, html: html) do - new() - |> to(to) - |> from(from) - |> subject(subject) - |> text_body(text) - |> html_body(html) - end - def gmail(%{to: to, from: from, subject: subject, text: text} = params) do new() |> to(to) @@ -59,396 +40,4 @@ defmodule ChatApi.Emails.Email do end def prepare_gmail_attachments(message, _), do: message - - # TODO: Add some recent messages for context, rather than just a single message - # (See the `conversation_reply` method for an example of this) - def new_message_alert( - to_address, - %Message{ - body: body, - conversation_id: conversation_id, - customer_id: customer_id - } = _message - ) do - customer = - case customer_id do - id when is_binary(id) -> ChatApi.Customers.get_customer!(id) - _ -> nil - end - - {subject, intro} = - case customer do - %Customer{email: email, name: name} when is_binary(email) and is_binary(name) -> - {"#{name} (#{email}) has sent you a message", "New message from #{name} (#{email}):"} - - %Customer{email: email} when is_binary(email) -> - {"#{email} has sent you a message", "New message from #{email}:"} - - %Customer{name: name} when is_binary(name) -> - {"#{name} has sent you a message", "New message from #{name}:"} - - _ -> - {"A customer has sent you a message (conversation #{conversation_id})", - "New message from an anonymous user:"} - end - - link = - "View in dashboard" - - html = intro <> "
" <> "#{body}" <> "

" <> link - text = intro <> " " <> body - - new() - |> to(to_address) - |> from({"Papercups", @from_address}) - |> subject(subject) - |> html_body(html) - |> text_body(text) - end - - def conversation_reply( - to: to, - from: from, - reply_to: reply_to, - company: company, - messages: messages, - customer: customer - ) do - new() - |> to(to) - |> from({from, @from_address}) - |> reply_to(reply_to) - |> subject("New message from #{company}!") - |> html_body(conversation_reply_html(messages, from: from, to: customer, company: company)) - |> text_body(conversation_reply_text(messages, from: from, to: customer, company: company)) - end - - # TODO: figure out a better way to create templates for these - defp conversation_reply_text(messages, from: from, to: customer, company: company) do - """ - Hi #{customer.name || "there"}! - - You've received a new message from your chat with #{company} (#{customer.current_url || ""}): - - #{ - Enum.map(messages, fn msg -> - format_sender(msg, company) <> ": " <> msg.body <> "\n" - end) - } - - Best, - #{from} - """ - end - - defp format_agent(user, company) do - case user do - %{email: email, profile: nil} -> - company || email - - %{email: email, profile: %UserProfile{} = profile} -> - profile.display_name || profile.full_name || company || email - - _ -> - company || "Agent" - end - end - - defp format_sender(message, company) do - case message do - %{user: user, customer_id: nil} -> format_agent(user, company) - %{customer_id: _customer_id} -> "You" - end - end - - defp conversation_reply_html(messages, from: from, to: customer, company: company) do - """ -

Hi #{customer.name || "there"}!

-

You've received a new message from your chat with - #{company}:

-
- #{Enum.map(messages, fn msg -> format_message_html(msg, company) end)} -
-

- Best,
- #{from} -

- """ - end - - defp format_message_html(message, company) do - markdown = """ - **#{format_sender(message, company)}**\s\s - #{message.body} - """ - - fallback = """ -

- #{format_sender(message, company)}
- #{message.body} -

- """ - - case Earmark.as_html(markdown) do - {:ok, html, _} -> html - _ -> fallback - end - end - - def mention_notification( - to: to, - from: from, - reply_to: reply_to, - company: company, - messages: messages, - user: user - ) do - new() - |> to(to) - |> from({from, @from_address}) - |> reply_to(reply_to) - |> subject("You were mentioned in a message on Papercups!") - |> html_body(mention_notification_html(messages, from: from, to: user, company: company)) - |> text_body(mention_notification_text(messages, from: from, to: user, company: company)) - end - - # TODO: figure out a better way to create templates for these - defp mention_notification_text(messages, from: from, to: _user, company: company) do - conversation_id = messages |> List.first() |> Map.get(:conversation_id) - dashboard_link = "#{get_app_domain()}/conversations/mentions/#{conversation_id}" - - """ - Hi there! - - You were mentioned in a message on Papercups: - - #{ - Enum.map(messages, fn msg -> - format_sender(msg, company) <> ": " <> msg.body <> "\n" - end) - } - - View in the dashboard at #{dashboard_link} - - Best, - #{from} - """ - end - - defp mention_notification_html(messages, from: from, to: _user, company: company) do - conversation_id = messages |> List.first() |> Map.get(:conversation_id) - dashboard_link = "#{get_app_domain()}/conversations/mentions/#{conversation_id}" - - """ -

Hi there!

-

You were mentioned in a message on Papercups:

-
- #{Enum.map(messages, fn msg -> format_message_html(msg, company) end)} -
-

- (View in the dashboard) -

-

- Best,
- #{from} -

- """ - end - - # TODO: use env variables instead, come up with a better message - def welcome(to_address) do - new() - |> to(to_address) - |> from({"Alex", @from_address}) - |> reply_to("alex@papercups.io") - |> subject("Welcome to Papercups!") - |> html_body(welcome_email_html()) - |> text_body(welcome_email_text()) - end - - # TODO: figure out a better way to create templates for these - defp welcome_email_text() do - # TODO: include user's name if available - """ - Hi there! - - Thanks for signing up for Papercups :) - - I'm Alex, one of the founders of Papercups along with Kam. If you have any questions, - feedback, or need any help getting started, don't hesitate to reach out! - - Feel free to reply directly to this email, or contact me at alex@papercups.io - - Best, - Alex - - We also have a Slack channel if you'd like to see what we're up to :) - https://github.com/papercups-io/papercups#get-in-touch - """ - end - - # TODO: figure out a better way to create templates for these - defp welcome_email_html() do - # TODO: include user's name if available - """ -

Hi there!

- -

Thanks for signing up for Papercups :)

- -

I'm Alex, one of the founders of Papercups along with Kam. If you have any questions, - feedback, or need any help getting started, don't hesitate to reach out!

- -

Feel free to reply directly to this email, or contact me at alex@papercups.io

- -

- Best,
- Alex -

- -

- PS: We also have a Slack channel if you'd like to see what we're up to :)
- https://github.com/papercups-io/papercups#get-in-touch -

- """ - end - - def user_invitation( - %{ - company: company, - from_address: from_address, - from_name: from_name, - invitation_token: invitation_token, - to_address: to_address - } = _params - ) do - subject = - if from_name == company, - do: "You've been invited to join #{company} on Papercups!", - else: "#{from_name} has invited you to join #{company} on Papercups!" - - intro_line = - if from_name == company, - do: "#{from_address} has invited you to join #{company} on Papercups!", - else: "#{from_name} (#{from_address}) has invited you to join #{company} on Papercups!" - - invitation_url = - "#{get_app_domain()}/register/#{invitation_token}?#{URI.encode_query(%{email: to_address})}" - - new() - |> to(to_address) - |> from({"Alex", @from_address}) - |> reply_to("alex@papercups.io") - |> subject(subject) - |> html_body( - user_invitation_email_html(%{ - intro_line: intro_line, - invitation_url: invitation_url - }) - ) - |> text_body( - user_invitation_email_text(%{ - intro_line: intro_line, - invitation_url: invitation_url - }) - ) - end - - defp user_invitation_email_text( - %{ - invitation_url: invitation_url, - intro_line: intro_line - } = _params - ) do - """ - Hi there! - - #{intro_line} - - Click the link below to sign up: - - #{invitation_url} - - Best, - Alex & Kam @ Papercups - """ - end - - # TODO: figure out a better way to create templates for these - defp user_invitation_email_html( - %{ - invitation_url: invitation_url, - intro_line: intro_line - } = _params - ) do - """ -

Hi there!

- -

#{intro_line}

- -

Click the link below to sign up:

- - #{invitation_url} - -

- Best,
- Alex & Kam @ Papercups -

- """ - end - - def password_reset(%ChatApi.Users.User{email: email, password_reset_token: token} = _user) do - new() - |> to(email) - |> from({"Papercups", @from_address}) - |> subject("[Papercups] Link to reset your password") - |> html_body(password_reset_html(token)) - |> text_body(password_reset_text(token)) - end - - defp get_app_domain() do - if Application.get_env(:chat_api, :environment) == :dev do - "http://localhost:3000" - else - "https://" <> System.get_env("BACKEND_URL", "app.papercups.io") - end - end - - # TODO: figure out a better way to create templates for these - defp password_reset_text(token) do - """ - Hi there! - - Click the link below to reset your Papercups password: - - #{get_app_domain()}/reset?token=#{token} - - Best, - Alex & Kam @ Papercups - """ - end - - # TODO: figure out a better way to create templates for these - defp password_reset_html(token) do - link = "#{get_app_domain()}/reset?token=#{token}" - - """ -

Hi there!

- -

Click the link below to reset your Papercups password:

- - #{link} - -

- Best,
- Alex & Kam @ Papercups -

- """ - end - - @spec changeset(any(), map()) :: Ecto.Changeset.t() - def changeset(email, attrs) do - email - |> cast(attrs, [:to_address, :message]) - |> validate_required([:to_address, :message]) - end end diff --git a/lib/chat_api/mailbox.ex b/lib/chat_api/mailbox.ex new file mode 100644 index 000000000..3e663d3c3 --- /dev/null +++ b/lib/chat_api/mailbox.ex @@ -0,0 +1,94 @@ +defmodule ChatApi.Mailbox do + @moduledoc """ + A module to simulate interactions with the API client + """ + + require Logger + + use Tesla + + plug( + Tesla.Middleware.BaseUrl, + System.get_env("MAILBOX_API_BASE_URL", "") + ) + + plug(Tesla.Middleware.Headers, [ + {"content-type", "application/json; charset=utf-8"} + ]) + + plug(Tesla.Middleware.JSON) + plug(Tesla.Middleware.Logger) + + defmodule Email do + defstruct subject: "", + from: nil, + to: nil, + cc: nil, + bcc: nil, + text_body: nil, + html_body: nil, + attachments: [], + reply_to: nil, + headers: %{}, + # Custom + template: nil, + data: %{}, + # Scheduling + schedule_in: nil, + scheduled_at: nil, + # Idempotency/uniqueness + idempotent: false, + idempotency_key: nil, + idempotency_period: nil + end + + @spec send_email(binary(), map()) :: {:error, any()} | {:ok, Tesla.Env.t()} + def send_email(token, %Email{} = email) when is_binary(token), + do: + send_email(token, %{ + email: Map.from_struct(email), + credentials: default_credentials(), + # TODO: make this configurable? + validate: true + }) + + def send_email(token, %Swoosh.Email{} = email) when is_binary(token), + do: + send_email(token, %{ + email: + email + |> Map.from_struct() + |> Map.merge(%{data: email.assigns}) + |> Map.merge(email.private), + credentials: default_credentials(), + # TODO: make this configurable? + validate: true + }) + + def send_email(token, params) when is_binary(token) and is_map(params) do + post("/send", params, + headers: [ + {"Authorization", "Bearer " <> token} + ] + ) + end + + @spec send_email(map()) :: {:error, any()} | {:ok, Tesla.Env.t()} + def send_email(params) when is_map(params), + do: send_email(System.get_env("MAILBOX_API_KEY", ""), params) + + defp default_credentials() do + case System.get_env("MAILER_ADAPTER") do + "Swoosh.Adapters.Mailgun" -> + %{ + adapter: "mailgun" + # NB: avoiding this for now + # api_key: System.get_env("MAILGUN_API_KEY"), + # domain: System.get_env("DOMAIN") + } + + _ -> + %{adapter: "mailgun"} + end + end +end diff --git a/lib/chat_api/messages/notification.ex b/lib/chat_api/messages/notification.ex index 05a40242f..93e66022f 100644 --- a/lib/chat_api/messages/notification.ex +++ b/lib/chat_api/messages/notification.ex @@ -156,16 +156,8 @@ defmodule ChatApi.Messages.Notification do "Sending message notification: :conversation_reply_email (message #{inspect(message.id)})" ) - # 20 minutes (TODO: make this configurable?) - schedule_in = 20 * 60 - formatted = Helpers.format(message) - - # TODO: not sure the best way to handle this, but basically we want to only - # enqueue the latest message to trigger an email if it remains unseen for 2 mins - ChatApi.Workers.SendConversationReplyEmail.cancel_pending_jobs(formatted) - - %{message: formatted} - |> ChatApi.Workers.SendConversationReplyEmail.new(schedule_in: schedule_in) + %{message: Helpers.format(message)} + |> ChatApi.Workers.SendConversationReplyEmail.new() |> Oban.insert() message diff --git a/lib/chat_api/users.ex b/lib/chat_api/users.ex index a7f288d8e..48fd3fdec 100644 --- a/lib/chat_api/users.ex +++ b/lib/chat_api/users.ex @@ -206,16 +206,15 @@ defmodule ChatApi.Users do @spec get_user_info(integer()) :: User.t() | nil def get_user_info(user_id) do User - |> where(id: ^user_id) - |> Repo.one() + |> Repo.get(user_id) |> Repo.preload([:profile, :settings]) end @spec get_user_info(binary(), integer()) :: User.t() | nil def get_user_info(account_id, user_id) do User - |> where(id: ^user_id, account_id: ^account_id) - |> Repo.one() + |> where(account_id: ^account_id) + |> Repo.get(user_id) |> Repo.preload([:profile, :settings]) end diff --git a/lib/chat_api_web/controllers/user_invitation_email_controller.ex b/lib/chat_api_web/controllers/user_invitation_email_controller.ex index 4bdb2cd07..e6baac3ef 100644 --- a/lib/chat_api_web/controllers/user_invitation_email_controller.ex +++ b/lib/chat_api_web/controllers/user_invitation_email_controller.ex @@ -1,7 +1,9 @@ defmodule ChatApiWeb.UserInvitationEmailController do use ChatApiWeb, :controller - alias ChatApi.{Accounts, UserInvitations} + require Logger + + alias ChatApi.{Accounts, Users, UserInvitations} alias ChatApi.UserInvitations.UserInvitation plug ChatApiWeb.EnsureRolePlug, :admin when action in [:create] @@ -28,12 +30,22 @@ defmodule ChatApiWeb.UserInvitationEmailController do {:ok, %UserInvitation{} = user_invitation} = UserInvitations.create_user_invitation(%{account_id: current_user.account_id}) - enqueue_user_invitation_email( - current_user.id, - current_user.account_id, - to_address, - user_invitation.id - ) + if send_user_invitation_email_enabled?() do + user = Users.get_user_info(current_user.account_id, current_user.id) + account = Accounts.get_account!(current_user.account_id) + + Logger.info("Sending user invitation email to #{to_address}") + + result = + ChatApi.Emails.send_user_invitation_email( + user, + account, + to_address, + user_invitation.id + ) + + IO.inspect(result, label: "Sent user invitation email") + end conn |> put_status(:created) @@ -41,14 +53,11 @@ defmodule ChatApiWeb.UserInvitationEmailController do end end - def enqueue_user_invitation_email(user_id, account_id, to_address, invitation_token) do - %{ - user_id: user_id, - account_id: account_id, - to_address: to_address, - invitation_token: invitation_token - } - |> ChatApi.Workers.SendUserInvitationEmail.new() - |> Oban.insert() + @spec send_user_invitation_email_enabled? :: boolean() + defp send_user_invitation_email_enabled?() do + case System.get_env("USER_INVITATION_EMAIL_ENABLED") do + x when x == "1" or x == "true" -> true + _ -> false + end end end diff --git a/lib/workers/send_conversation_reply_email.ex b/lib/workers/send_conversation_reply_email.ex index f46c5f0d3..75e125c12 100644 --- a/lib/workers/send_conversation_reply_email.ex +++ b/lib/workers/send_conversation_reply_email.ex @@ -7,7 +7,7 @@ defmodule ChatApi.Workers.SendConversationReplyEmail do require Logger - alias ChatApi.{Accounts, Conversations, Messages, Repo, Users} + alias ChatApi.{Accounts, Conversations, Messages, Users} alias ChatApi.Customers.Customer alias ChatApi.Messages.Message @@ -80,23 +80,6 @@ defmodule ChatApi.Workers.SendConversationReplyEmail do |> Enum.reverse() end - @spec get_pending_job_ids(binary()) :: [integer()] - def get_pending_job_ids(conversation_id) do - # TODO: double check this logic - Oban.Job - |> where(worker: "ChatApi.Workers.SendConversationReplyEmail") - |> where([j], j.state != "discarded") - |> Repo.all() - |> Enum.filter(fn job -> job.args["message"]["conversation_id"] == conversation_id end) - |> Enum.map(fn job -> job.id end) - end - - def cancel_pending_jobs(%{conversation_id: conversation_id}) do - conversation_id - |> get_pending_job_ids() - |> Enum.map(fn id -> Oban.cancel_job(id) end) - end - @doc """ Check if we should send a notification email. Note that we only want to send these if the source is "chat" (we don't want to send when source is "slack") diff --git a/test/workers/send_conversation_reply_email_test.exs b/test/workers/send_conversation_reply_email_test.exs index f0096a484..4326799dc 100644 --- a/test/workers/send_conversation_reply_email_test.exs +++ b/test/workers/send_conversation_reply_email_test.exs @@ -124,47 +124,47 @@ defmodule ChatApi.SendConversationReplyEmailTest do end) =~ "Skipped sending" end - test "formats the email properly", %{ - account: account, - customer: customer, - user: user - } do - conversation = insert(:conversation, account: account, customer: customer, source: "chat") - - messages = [ - insert(:message, - account: account, - conversation: conversation, - user: user, - body: "This is a test with plain text", - customer: nil, - seen_at: nil - ), - insert(:message, - account: account, - conversation: conversation, - user: user, - body: "This is a test _with_ **markdown** [woot](https://papercups.io)", - customer: nil, - seen_at: nil - ) - ] - - email = - ChatApi.Emails.Email.conversation_reply( - to: customer.email, - from: "Papercups Test", - reply_to: user.email, - company: account.company_name, - messages: messages, - customer: customer - ) - - assert email.html_body =~ "This is a test with plain text" - - assert email.html_body =~ - "This is a test with markdown woot" - end + # test "formats the email properly", %{ + # account: account, + # customer: customer, + # user: user + # } do + # conversation = insert(:conversation, account: account, customer: customer, source: "chat") + + # messages = [ + # insert(:message, + # account: account, + # conversation: conversation, + # user: user, + # body: "This is a test with plain text", + # customer: nil, + # seen_at: nil + # ), + # insert(:message, + # account: account, + # conversation: conversation, + # user: user, + # body: "This is a test _with_ **markdown** [woot](https://papercups.io)", + # customer: nil, + # seen_at: nil + # ) + # ] + + # email = + # ChatApi.Emails.Email.conversation_reply( + # to: customer.email, + # from: "Papercups Test", + # reply_to: user.email, + # company: account.company_name, + # messages: messages, + # customer: customer + # ) + + # assert email.html_body =~ "This is a test with plain text" + + # assert email.html_body =~ + # "This is a test with markdown woot" + # end test "handles invalid input" do assert :error =