Current section
Files
Jump to
Current section
Files
lib/ex_aws/ses.ex
defmodule ExAws.SES do
import ExAws.Utils, only: [camelize_key: 1, camelize_keys: 1]
@moduledoc """
Operations on AWS SES
http://docs.aws.amazon.com/ses/latest/APIReference/Welcome.html
"""
@notification_types [:bounce, :complaint, :delivery]
@doc "Verifies an email address"
@spec verify_email_identity(email :: binary) :: ExAws.Operation.Query.t()
def verify_email_identity(email) do
request(:verify_email_identity, %{"EmailAddress" => email})
end
@type list_identities_opt ::
{:max_items, pos_integer}
| {:next_token, String.t()}
| {:identity_type, String.t()}
@doc "List identities associated with the AWS account"
@spec list_identities(opts :: [] | [list_identities_opt]) :: ExAws.Operation.Query.t()
def list_identities(opts \\ []) do
params = build_opts(opts, [:max_items, :next_token, :identity_type])
request(:list_identities, params)
end
@doc "Fetch identities verification status and token (for domains)"
@spec get_identity_verification_attributes([binary]) :: ExAws.Operation.Query.t()
def get_identity_verification_attributes(identities) when is_list(identities) do
params = format_member_attribute({:identities, identities})
request(:get_identity_verification_attributes, params)
end
@type list_configuration_sets_opt ::
{:max_items, pos_integer}
| {:next_token, String.t()}
@doc "Fetch configuration sets associated with AWS account"
@spec list_configuration_sets() :: ExAws.Operation.Query.t()
@spec list_configuration_sets(opts :: [] | [list_configuration_sets_opt]) :: ExAws.Operation.Query.t()
def list_configuration_sets(opts \\ []) do
params = build_opts(opts, [:max_items, :next_token])
request(:list_configuration_sets, params)
end
## Emails
######################
@type email_address :: binary
@type message :: %{
body: %{html: %{data: binary, charset: binary}, text: %{data: binary, charset: binary}},
subject: %{data: binary, charset: binary}
}
@type destination :: %{to: [email_address], cc: [email_address], bcc: [email_address]}
@type send_email_opt ::
{:configuration_set_name, String.t()}
| {:reply_to, [email_address]}
| {:return_path, String.t()}
| {:return_path_arn, String.t()}
| {:source, String.t()}
| {:source_arn, String.t()}
| {:tags, %{(String.t() | atom) => String.t()}}
@doc "Composes an email message"
@spec send_email(dst :: destination, msg :: message, src :: binary) :: ExAws.Operation.Query.t()
@spec send_email(dst :: destination, msg :: message, src :: binary, opts :: [send_email_opt]) ::
ExAws.Operation.Query.t()
def send_email(dst, msg, src, opts \\ []) do
dst =
Enum.reduce([:to, :bcc, :cc], %{}, fn key, acc ->
case Map.fetch(dst, key) do
{:ok, val} -> Map.put(acc, :"#{key}_addresses", val)
_ -> acc
end
end)
params =
opts
|> build_opts([:configuration_set_name, :return_path, :return_path_arn, :source_arn, :bcc])
|> Map.merge(format_member_attribute(:reply_to_addresses, opts[:reply_to]))
|> Map.merge(flatten_attrs(msg, "message"))
|> Map.merge(format_tags(opts[:tags]))
|> Map.merge(format_dst(dst))
|> Map.put_new("Source", src)
request(:send_email, params)
end
@doc """
Send a raw Email.
"""
@type send_raw_email_opt ::
{:configuration_set_name, String.t()}
| {:from_arn, String.t()}
| {:return_path_arn, String.t()}
| {:source, String.t()}
| {:source_arn, String.t()}
| {:tags, %{(String.t() | atom) => String.t()}}
@spec send_raw_email(binary, opts :: [send_raw_email_opt]) :: ExAws.Operation.Query.t()
def send_raw_email(raw_msg, opts \\ []) do
params =
opts
|> camelize_keys
|> Map.merge(format_tags(opts[:tags]))
|> Map.put("RawMessage.Data", Base.encode64(raw_msg))
request(:send_raw_email, params)
end
@doc """
Send a templated Email.
"""
@type send_templated_email_opt ::
{:configuration_set_name, String.t()}
| {:return_path, String.t()}
| {:return_path_arn, String.t()}
| {:source, String.t()}
| {:source_arn, String.t()}
| {:tags, %{(String.t() | atom) => String.t()}}
@spec send_templated_email(
dst :: destination,
src :: binary,
template :: binary,
template_data :: binary,
opts :: [send_templated_email_opt]
) :: ExAws.Operation.Query.t()
def send_templated_email(dst, src, template, template_data, opts \\ []) do
dst =
Enum.reduce([:to, :bcc, :cc], %{}, fn key, acc ->
case Map.fetch(dst, key) do
{:ok, val} -> Map.put(acc, :"#{key}_addresses", val)
_ -> acc
end
end)
params =
opts
|> build_opts([:configuration_set_name, :return_path, :return_path_arn, :source_arn, :bcc])
|> Map.merge(format_member_attribute(:reply_to_addresses, opts[:reply_to]))
|> Map.merge(format_tags(opts[:tags]))
|> Map.merge(format_dst(dst))
|> Map.put_new("Source", src)
|> Map.put_new("Template", template)
|> Map.put_new("TemplateData", template_data)
request(:send_templated_email, params)
end
@doc "Deletes the specified identity (an email address or a domain) from the list of verified identities."
@spec delete_identity(binary) :: ExAws.Operation.Query.t()
def delete_identity(identity) do
request(:delete_identity, %{"Identity" => identity})
end
@type set_identity_notification_topic_opt :: {:sns_topic, binary}
@type notification_type :: :bounce | :complaint | :delivery
@doc """
Sets the Amazon Simple Notification Service (Amazon SNS) topic to which Amazon SES will publish delivery
notifications for emails sent with given identity.
Absent `sns_topic` options cleans SnsTopic and disables publishing.
Notification type can be on of the :bounce, :complaint or :delivery.
Requests are throttled to one per second.
"""
@spec set_identity_notification_topic(binary, notification_type, set_identity_notification_topic_opt | []) ::
ExAws.Operation.Query.t()
def set_identity_notification_topic(identity, type, opts \\ []) when type in @notification_types do
notification_type = Atom.to_string(type) |> String.capitalize()
params =
opts
|> build_opts([:sns_topic])
|> Map.merge(%{"Identity" => identity, "NotificationType" => notification_type})
request(:set_identity_notification_topic, params)
end
@doc "Enables or disables whether Amazon SES forwards notifications as email"
@spec set_identity_feedback_forwarding_enabled(boolean, binary) :: ExAws.Operation.Query.t()
def set_identity_feedback_forwarding_enabled(enabled, identity) do
request(:set_identity_feedback_forwarding_enabled, %{"ForwardingEnabled" => enabled, "Identity" => identity})
end
@doc "Build message object"
@spec build_message(binary, binary, binary, binary) :: message
def build_message(html, txt, subject, charset \\ "UTF-8") do
%{
body: %{
html: %{data: html, charset: charset},
text: %{data: txt, charset: charset}
},
subject: %{data: subject, charset: charset}
}
end
@doc "Set whether SNS notifications should include original email headers or not"
@spec set_identity_headers_in_notifications_enabled(binary, notification_type, boolean) :: ExAws.Operation.Query.t()
def set_identity_headers_in_notifications_enabled(identity, type, enabled) do
notification_type = Atom.to_string(type) |> String.capitalize()
request(
:set_identity_headers_in_notifications_enabled,
%{"Identity" => identity, "NotificationType" => notification_type, "Enabled" => enabled}
)
end
defp format_dst(dst) do
dst
|> Map.to_list()
|> format_member_attributes([:bcc_addresses, :cc_addresses, :to_addresses])
|> flatten_attrs("destination")
end
defp format_tags(nil), do: %{}
defp format_tags(tags) do
tags
|> Enum.with_index(1)
|> Enum.reduce(%{}, fn {tag, index}, acc ->
key = camelize_key("tags.member.#{index}")
Map.merge(acc, flatten_attrs(tag, key))
end)
end
## Request
######################
defp request(action, params) do
action_string = action |> Atom.to_string() |> Macro.camelize()
%ExAws.Operation.Query{
path: "/",
params: params |> Map.put("Action", action_string),
service: :ses,
action: action,
parser: &ExAws.SES.Parsers.parse/2
}
end
defp build_opts(opts, permitted) do
opts
|> Map.new()
|> Map.take(permitted)
|> camelize_keys
end
defp format_member_attributes(opts, members) do
opts
|> Map.new()
|> Map.take(members)
|> Enum.reduce(Map.new(opts), fn entry, acc -> Map.merge(acc, format_member_attribute(entry)) end)
|> Map.drop(members)
end
defp format_member_attribute(key, collection), do: format_member_attribute({key, collection})
defp format_member_attribute({_, nil}), do: %{}
defp format_member_attribute({key, collection}) do
collection
|> Enum.with_index(1)
|> Map.new(fn {item, index} ->
{"#{camelize_key(key)}.member.#{index}", item}
end)
end
defp flatten_attrs(attrs, root) do
do_flatten_attrs({attrs, camelize_key(root)})
|> List.flatten()
|> Map.new()
end
defp do_flatten_attrs({attrs, root}) when is_map(attrs) do
Enum.map(attrs, fn {k, v} ->
do_flatten_attrs({v, root <> "." <> camelize_key(k)})
end)
end
defp do_flatten_attrs({val, path}) do
{camelize_key(path), val}
end
end