├── test
├── test_helper.exs
└── lib
│ ├── ses
│ └── parser_test.exs
│ └── ses_test.exs
├── CONTRIBUTING.md
├── .formatter.exs
├── ISSUE_TEMPLATE.md
├── .gitignore
├── config
└── config.exs
├── mix.exs
├── README.md
├── CHANGELOG.md
├── mix.lock
└── lib
└── ex_aws
├── ses
└── parsers.ex
└── ses.ex
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start(exclude: [:integration])
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | Contributions to ExAws are always welcome! For contributions to this particular service, feel free to just open a PR or an issue. For larger scale contributions see: https://github.com/ex-aws/ex_aws/blob/master/CONTRIBUTING.md
5 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # src https://gist.github.com/dberget/f4d157603a90cda95f289c06858dd04c
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
4 | locals_without_parens: [
5 | # Kernel
6 | inspect: 1,
7 | inspect: 2,
8 |
9 | # Tests
10 | assert: 1,
11 | assert: 2,
12 | assert: 3,
13 | on_exit: 1
14 | ],
15 | line_length: 120
16 | ]
17 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * Do not use the issues tracker for help or support (try Elixir Forum, Slack, IRC, etc.)
2 | * Questions about how to contribute are fine.
3 |
4 | ### Environment
5 |
6 | * Elixir & Erlang versions (elixir --version):
7 | * ExAws version `mix deps |grep ex_aws`
8 | * HTTP client version. IE for hackney do `mix deps | grep hackney`
9 |
10 | ### Current behavior
11 |
12 | Include code samples, errors and stacktraces if appropriate.
13 |
14 | ### Expected behavior
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | ex_aws_ses-*.tar
24 |
25 | # Temporary files for e.g. tests.
26 | /tmp/
27 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | import Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # 3rd-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :ex_aws_ses, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:ex_aws_ses, :key)
18 | #
19 | # You can also configure a 3rd-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env}.exs"
31 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAws.SES.Mixfile do
2 | use Mix.Project
3 |
4 | @version "2.4.1"
5 | @service "ses"
6 | @url "https://github.com/ex-aws/ex_aws_#{@service}"
7 | @name __MODULE__ |> Module.split() |> Enum.take(2) |> Enum.join(".")
8 |
9 | def project do
10 | [
11 | app: :ex_aws_ses,
12 | version: @version,
13 | elixir: "~> 1.5",
14 | elixirc_paths: elixirc_paths(Mix.env()),
15 | start_permanent: Mix.env() == :prod,
16 | deps: deps(),
17 | name: @name,
18 | package: package(),
19 | docs: docs()
20 | ]
21 | end
22 |
23 | defp elixirc_paths(:test), do: ["lib", "test/support"]
24 | defp elixirc_paths(_), do: ["lib"]
25 |
26 | # Run "mix help compile.app" to learn about applications.
27 | def application do
28 | [
29 | extra_applications: [:logger]
30 | ]
31 | end
32 |
33 | defp package do
34 | [
35 | description: "#{@name} service package",
36 | files: ["lib", "config", "mix.exs", "CHANGELOG*", "README*"],
37 | maintainers: ["Ben Wilson"],
38 | licenses: ["MIT"],
39 | links: %{
40 | Changelog: "https://hexdocs.pm/ex_aws_ses/changelog.html",
41 | GitHub: @url
42 | }
43 | ]
44 | end
45 |
46 | # Run "mix help deps" to learn about dependencies.
47 | defp deps do
48 | [
49 | {:hackney, ">= 0.0.0", only: [:dev, :test]},
50 | {:sweet_xml, ">= 0.0.0", only: [:dev, :test]},
51 | {:jason, ">= 0.0.0", only: [:dev, :test]},
52 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false},
53 | {:ex_doc, ">= 0.0.0", only: [:dev, :test], runtime: false},
54 | ex_aws()
55 | ]
56 | end
57 |
58 | defp docs do
59 | [
60 | extras: ["CHANGELOG.md", "README.md"],
61 | main: "readme",
62 | source_url: @url,
63 | source_ref: "v#{@version}",
64 | formatters: ["html"]
65 | ]
66 | end
67 |
68 | defp ex_aws() do
69 | case System.get_env("AWS") do
70 | "LOCAL" -> {:ex_aws, path: "../ex_aws"}
71 | _ -> {:ex_aws, "~> 2.0"}
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExAws.SES
2 |
3 | [](https://hex.pm/packages/ex_aws_ses)
4 | [](https://hexdocs.pm/ex_aws_ses/)
5 | [](https://hex.pm/packages/ex_aws_ses)
6 | [](https://github.com/ex-aws/ex_aws_ses/blob/master/LICENSE)
7 | [](https://github.com/ex-aws/ex_aws_ses/commits/master)
8 |
9 | Service module for [https://github.com/ex-aws/ex_aws](https://github.com/ex-aws/ex_aws).
10 |
11 | ## Installation
12 |
13 | The package can be installed by adding `:ex_aws_ses` to your list of dependencies in `mix.exs`
14 | along with `:ex_aws` and your preferred JSON codec / HTTP client
15 |
16 | ```elixir
17 | def deps do
18 | [
19 | {:ex_aws, "~> 2.0"},
20 | {:ex_aws_ses, "~> 2.0"},
21 | {:poison, "~> 3.0"},
22 | {:hackney, "~> 1.9"},
23 | ]
24 | end
25 | ```
26 |
27 | Documentation can be found at [https://hexdocs.pm/ex_aws_ses](https://hexdocs.pm/ex_aws_ses).
28 |
29 | ## License
30 |
31 | The MIT License (MIT)
32 |
33 | Copyright (c) 2014 CargoSense, Inc.
34 |
35 | Permission is hereby granted, free of charge, to any person obtaining a copy
36 | of this software and associated documentation files (the "Software"), to deal
37 | in the Software without restriction, including without limitation the rights
38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39 | copies of the Software, and to permit persons to whom the Software is
40 | furnished to do so, subject to the following conditions:
41 |
42 | The above copyright notice and this permission notice shall be included in
43 | all copies or substantial portions of the Software.
44 |
45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
51 | THE SOFTWARE.
52 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## v2.4.1 - 2021-03-03
11 |
12 | - Fix email address encoding in `PutSuppressedDestination` `DeleteSuppressedDestination` by @mtarnovan
13 | - Switch to from Poison to Jason
14 |
15 | ## v2.4.0 - 2022-03-21
16 |
17 | - Add v2 API's [PutSuppressedDestination](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_PutSuppressedDestination.html) by @mtarnovan
18 | - Add v2 API's [DeleteSuppressedDestination](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteSuppressedDestination.html) by @mtarnovan
19 |
20 | ## v2.3.0 - 2021-05-02
21 |
22 | - Add functions for custom verification emails by @wmnnd
23 |
24 | ## v2.2.0 - 2021-04-23
25 |
26 | - Add CRUD actions on contact list and contact resources by j4p3
27 | - Add v2 API's [SendEmail](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html) action by j4p3
28 |
29 | ## v2.1.1 - 2019-09-12
30 |
31 | - Added support of `ReplyToAddresses.member.N` option to [SendBulkTemplatedEmail](https://docs.aws.amazon.com/ses/latest/APIReference/API_SendBulkTemplatedEmail.html) action by @flyrboy96
32 | - Spec improvements by @flyrboy96
33 |
34 | ## v2.1.0 - 2019-03-09
35 |
36 | - Added support of [CreateTemplate](https://docs.aws.amazon.com/ses/latest/APIReference/API_CreateTemplate.html) action by @themerch
37 | - Added support of [DeleteTemplate](https://docs.aws.amazon.com/ses/latest/APIReference/API_DeleteTemplate.html) action by @themerch
38 | - Added support of [SendBulkTemplatedEmail](https://docs.aws.amazon.com/ses/latest/APIReference/API_SendBulkTemplatedEmail.html) action by @themerch
39 |
40 | ## v2.0.2 - 2018-08-08
41 |
42 | - Added support for [SendTemplatedEmail](https://docs.aws.amazon.com/ses/latest/APIReference/API_SendTemplatedEmail.html) action with exception for two optional parameters: `TemplateArn` and `ReplyToAddresses.member.N`. by @xfumihiro
43 | - Fixed wrong key in destination typespec by @kalys
44 | - Fixed broken typespec contracts
45 |
46 | ## v2.0.1 - 2018-06-27
47 |
48 | - Improved Mix configuration
49 |
50 | ## v2.0.0 - 2017-11-10
51 |
52 | - Major Project Split. Please see the main ExAws repository for previous changelogs.
53 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
3 | "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
4 | "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm", "000aaeff08919e95e7aea13e4af7b2b9734577b3e6a7c50ee31ee88cab6ec4fb"},
5 | "earmark_parser": {:hex, :earmark_parser, "1.4.24", "344f8d2a558691d3fcdef3f9400157d7c4b3b8e58ee5063297e9ae593e8326d9", [:mix], [], "hexpm", "1f6451b0116dd270449c8f5b30289940ee9c0a39154c783283a08e55af82ea34"},
6 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
7 | "ex_aws": {:hex, :ex_aws, "2.2.10", "064139724335b00b6665af7277189afc9ed507791b1ccf2698dadc7c8ad892e8", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98acb63f74b2f0822be219c5c2f0e8d243c2390f5325ad0557b014d3360da47e"},
8 | "ex_doc": {:hex, :ex_doc, "0.28.2", "e031c7d1a9fc40959da7bf89e2dc269ddc5de631f9bd0e326cbddf7d8085a9da", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "51ee866993ffbd0e41c084a7677c570d0fc50cb85c6b5e76f8d936d9587fa719"},
9 | "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
10 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
11 | "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
12 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
13 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
14 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
15 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
16 | "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
17 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
18 | "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
19 | "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
20 | "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
21 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
22 | "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"},
23 | "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
24 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
25 | }
26 |
--------------------------------------------------------------------------------
/lib/ex_aws/ses/parsers.ex:
--------------------------------------------------------------------------------
1 | if Code.ensure_loaded?(SweetXml) do
2 | defmodule ExAws.SES.Parsers do
3 | import SweetXml, only: [sigil_x: 2]
4 |
5 | @moduledoc false
6 |
7 | def parse({:ok, %{body: xml} = resp}, :verify_email_identity) do
8 | parsed_body = SweetXml.xpath(xml, ~x"//VerifyEmailIdentityResponse", request_id: request_id_xpath())
9 |
10 | {:ok, Map.put(resp, :body, parsed_body)}
11 | end
12 |
13 | def parse({:ok, %{body: xml} = resp}, :verify_domain_identity) do
14 | parsed_body = SweetXml.xpath(xml, ~x"//VerifyDomainIdentityResponse",
15 | verification_token: ~x"./VerifyDomainIdentityResult/VerificationToken/text()"s,
16 | request_id: request_id_xpath()
17 | )
18 |
19 | {:ok, Map.put(resp, :body, parsed_body)}
20 | end
21 |
22 | def parse({:ok, %{body: xml} = resp}, :verify_domain_dkim) do
23 | parsed_body = SweetXml.xpath(xml, ~x"//VerifyDomainDkimResponse",
24 | dkim_tokens: [
25 | ~x"./VerifyDomainDkimResult",
26 | members: ~x"./DkimTokens/member/text()"ls
27 | ],
28 | request_id: request_id_xpath()
29 | )
30 |
31 | {:ok, Map.put(resp, :body, parsed_body)}
32 | end
33 |
34 |
35 | def parse({:ok, %{body: xml} = resp}, :get_identity_verification_attributes) do
36 | parsed_body =
37 | xml
38 | |> SweetXml.xpath(~x"//GetIdentityVerificationAttributesResponse",
39 | verification_attributes: [
40 | ~x"./GetIdentityVerificationAttributesResult/VerificationAttributes/entry"l,
41 | entry: ~x"./key/text()"s,
42 | verification_status: ~x"./value/VerificationStatus/text()"s,
43 | verification_token: ~x"./value/VerificationToken/text()"so
44 | ],
45 | request_id: request_id_xpath()
46 | )
47 | |> update_in([:verification_attributes], &verification_attributes_list_to_map/1)
48 |
49 | {:ok, Map.put(resp, :body, parsed_body)}
50 | end
51 |
52 | def parse({:ok, %{body: xml} = resp}, :list_identities) do
53 | parsed_body =
54 | xml
55 | |> SweetXml.xpath(~x"//ListIdentitiesResponse",
56 | custom_verification_email_templates: [
57 | ~x"./ListIdentitiesResult",
58 | members: ~x"./Identities/member/text()"ls,
59 | next_token: ~x"./NextToken/text()"so
60 | ], #TODO: Remove this key in the next major version, 3.x.x
61 | identities: [
62 | ~x"./ListIdentitiesResult",
63 | members: ~x"./Identities/member/text()"ls,
64 | next_token: ~x"./NextToken/text()"so
65 | ],
66 | request_id: request_id_xpath()
67 | )
68 |
69 | {:ok, Map.put(resp, :body, parsed_body)}
70 | end
71 |
72 | def parse({:ok, %{body: xml} = resp}, :list_configuration_sets) do
73 | parsed_body =
74 | xml
75 | |> SweetXml.xpath(~x"//ListConfigurationSetsResponse",
76 | configuration_sets: [
77 | ~x"./ListConfigurationSetsResult",
78 | members: ~x"./ConfigurationSets/member/Name/text()"ls,
79 | next_token: ~x"./NextToken/text()"so
80 | ],
81 | request_id: request_id_xpath()
82 | )
83 |
84 | {:ok, Map.put(resp, :body, parsed_body)}
85 | end
86 |
87 | def parse({:ok, %{body: xml} = resp}, :send_email) do
88 | parsed_body =
89 | xml
90 | |> SweetXml.xpath(~x"//SendEmailResponse",
91 | message_id: ~x"./SendEmailResult/MessageId/text()"s,
92 | request_id: request_id_xpath()
93 | )
94 |
95 | {:ok, Map.put(resp, :body, parsed_body)}
96 | end
97 |
98 | def parse({:ok, %{body: xml} = resp}, :send_templated_email) do
99 | parsed_body =
100 | xml
101 | |> SweetXml.xpath(~x"//SendTemplatedEmailResponse",
102 | message_id: ~x"./SendTemplatedEmailResult/MessageId/text()"s,
103 | request_id: request_id_xpath()
104 | )
105 |
106 | {:ok, Map.put(resp, :body, parsed_body)}
107 | end
108 |
109 | def parse({:ok, %{body: xml} = resp}, :send_bulk_templated_email) do
110 | parsed_body =
111 | xml
112 | |> SweetXml.xmap(
113 | messages: [
114 | ~x[//SendBulkTemplatedEmailResponse/SendBulkTemplatedEmailResult/Status/member]l,
115 | message_id: ~x"./MessageId/text()"s,
116 | status: ~x"./Status/text()"s
117 | ],
118 | request_id: request_id_xpath()
119 | )
120 |
121 | {:ok, Map.put(resp, :body, parsed_body)}
122 | end
123 |
124 | def parse({:ok, %{body: xml} = resp}, :send_raw_email) do
125 | parsed_body =
126 | xml
127 | |> SweetXml.xpath(~x"//SendRawEmailResponse",
128 | message_id: ~x"./SendRawEmailResult/MessageId/text()"s,
129 | request_id: request_id_xpath()
130 | )
131 |
132 | {:ok, Map.put(resp, :body, parsed_body)}
133 | end
134 |
135 | def parse({:ok, %{body: xml} = resp}, :delete_identity) do
136 | parsed_body = SweetXml.xpath(xml, ~x"//DeleteIdentityResponse", request_id: request_id_xpath())
137 |
138 | {:ok, Map.put(resp, :body, parsed_body)}
139 | end
140 |
141 | def parse({:ok, %{body: xml} = resp}, :set_identity_notification_topic) do
142 | parsed_body = SweetXml.xpath(xml, ~x"//SetIdentityNotificationTopicResponse", request_id: request_id_xpath())
143 |
144 | {:ok, Map.put(resp, :body, parsed_body)}
145 | end
146 |
147 | def parse({:ok, %{body: xml} = resp}, :set_identity_feedback_forwarding_enabled) do
148 | parsed_body =
149 | SweetXml.xpath(
150 | xml,
151 | ~x"//SetIdentityFeedbackForwardingEnabledResponse",
152 | request_id: request_id_xpath()
153 | )
154 |
155 | {:ok, Map.put(resp, :body, parsed_body)}
156 | end
157 |
158 | def parse({:ok, %{body: xml} = resp}, :set_identity_headers_in_notifications_enabled) do
159 | parsed_body =
160 | SweetXml.xpath(
161 | xml,
162 | ~x"//SetIdentityHeadersInNotificationsEnabledResponse",
163 | request_id: request_id_xpath()
164 | )
165 |
166 | {:ok, Map.put(resp, :body, parsed_body)}
167 | end
168 |
169 | def parse({:ok, %{body: xml} = resp}, :create_template) do
170 | parsed_body = SweetXml.xpath(xml, ~x"//CreateTemplateResponse", request_id: request_id_xpath())
171 |
172 | {:ok, Map.put(resp, :body, parsed_body)}
173 | end
174 |
175 | def parse({:ok, %{body: xml} = resp}, :delete_template) do
176 | parsed_body = SweetXml.xpath(xml, ~x"//DeleteTemplateResponse", request_id: request_id_xpath())
177 |
178 | {:ok, Map.put(resp, :body, parsed_body)}
179 | end
180 |
181 | def parse({:ok, %{body: xml} = resp}, :list_custom_verification_email_templates) do
182 | parsed_body =
183 | xml
184 | |> SweetXml.xpath(~x"//ListCustomVerificationEmailTemplatesResponse",
185 | custom_verification_email_templates: [
186 | ~x"./ListCustomVerificationEmailTemplatesResult",
187 | members: [
188 | ~x"./CustomVerificationEmailTemplates/member"l,
189 | template_name: ~x"./TemplateName/text()"so,
190 | from_email_address: ~x"./FromEmailAddress/text()"so,
191 | template_subject: ~x"./TemplateSubject/text()"so,
192 | success_redirection_url: ~x"./SuccessRedirectionURL/text()"so,
193 | failure_redirection_url: ~x"./FailureRedirectionURL/text()"so
194 | ],
195 | next_token: ~x"./NextToken/text()"so
196 | ],
197 | request_id: request_id_xpath()
198 | )
199 |
200 | {:ok, Map.put(resp, :body, parsed_body)}
201 | end
202 |
203 | def parse({:ok, %{body: xml} = resp}, :describe_receipt_rule_set) do
204 | parsed_body = SweetXml.xpath(xml, ~x"//DescribeReceiptRuleSetResponse",
205 | rules: [
206 | ~x"./DescribeReceiptRuleSetResult",
207 | members: [
208 | ~x"./Rules/member"l,
209 | enabled: ~x"./Enabled/text()"so |> transform_to_boolean(),
210 | name: ~x"./Name/text()"s,
211 | recipients: ~x"./Recipients/member/text()"ls,
212 | scan_enabled: ~x"./ScanEnabled/text()"so |> transform_to_boolean(),
213 | tls_policy: ~x"./TlsPolicy/text()"so,
214 | ],
215 | ],
216 | request_id: request_id_xpath()
217 | )
218 |
219 | {:ok, Map.put(resp, :body, parsed_body)}
220 | end
221 |
222 | def parse({:error, {type, http_status_code, %{body: xml}}}, _) do
223 | parsed_body =
224 | xml
225 | |> SweetXml.xpath(~x"//ErrorResponse",
226 | request_id: ~x"./RequestId/text()"s,
227 | type: ~x"./Error/Type/text()"s,
228 | code: ~x"./Error/Code/text()"s,
229 | message: ~x"./Error/Message/text()"s,
230 | detail: ~x"./Error/Detail/text()"s
231 | )
232 |
233 | {:error, {type, http_status_code, parsed_body}}
234 | end
235 |
236 | def parse(val, _), do: val
237 |
238 | defp request_id_xpath do
239 | ~x"./ResponseMetadata/RequestId/text()"s
240 | end
241 |
242 | defp verification_attributes_list_to_map(attributes) do
243 | Enum.reduce(attributes, %{}, fn %{entry: key} = attribute, acc ->
244 | props =
245 | attribute
246 | |> Map.delete(:entry)
247 | |> Enum.reject(fn kv -> elem(kv, 1) in ["", nil] end)
248 | |> Enum.into(%{})
249 |
250 | Map.put_new(acc, key, props)
251 | end)
252 | end
253 |
254 | defp transform_to_boolean(arg) do
255 | SweetXml.transform_by(arg, fn
256 | "false" -> false
257 | "true" -> true
258 | _ -> nil
259 | end)
260 | end
261 | end
262 | else
263 | defmodule ExAws.SES.Parsers do
264 | @moduledoc false
265 |
266 | def parse(val, _), do: val
267 | end
268 | end
269 |
--------------------------------------------------------------------------------
/test/lib/ses/parser_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAws.SES.ParserTest do
2 | use ExUnit.Case, async: true
3 |
4 | alias ExAws.SES.Parsers
5 |
6 | defp to_success(doc) do
7 | {:ok, %{body: doc}}
8 | end
9 |
10 | defp to_error(doc) do
11 | {:error, {:http_error, 403, %{body: doc}}}
12 | end
13 |
14 | test "#parse a verify_email_identity response" do
15 | rsp =
16 | """
17 |
18 |
19 |
20 | d8eb8250-be9b-11e6-b7f7-d570946af758
21 |
22 |
23 | """
24 | |> to_success
25 |
26 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :verify_email_identity)
27 | assert parsed_doc == %{request_id: "d8eb8250-be9b-11e6-b7f7-d570946af758"}
28 | end
29 |
30 | test "#parse a verify_domain_identity response" do
31 | rsp =
32 | """
33 |
34 |
35 | u4GmlJ3cPJfxxZbLSPMkLOPjQvJW1HPvA6Pmi21CPIE=
36 |
37 |
38 | d8eb8250-be9b-11e6-b7f7-d570946af758
39 |
40 |
41 | """
42 | |> to_success
43 |
44 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :verify_domain_identity)
45 | assert parsed_doc == %{request_id: "d8eb8250-be9b-11e6-b7f7-d570946af758", verification_token: "u4GmlJ3cPJfxxZbLSPMkLOPjQvJW1HPvA6Pmi21CPIE="}
46 | end
47 |
48 | test "#parse a verify_domain_dkim response" do
49 | rsp =
50 | """
51 |
52 |
53 |
54 | 5livxhounddpfqprdog22m4c337ake5o
55 | tbnwx5g3l0zmstwf2c258r36pvpnksbt
56 | bbtl43drumsloilm2zfjlhj3c7v12a5d
57 |
58 |
59 |
60 | d8eb8250-be9b-11e6-b7f7-d570946af758
61 |
62 |
63 | """
64 | |> to_success
65 |
66 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :verify_domain_dkim)
67 | assert parsed_doc == %{request_id: "d8eb8250-be9b-11e6-b7f7-d570946af758", dkim_tokens: %{members: ["5livxhounddpfqprdog22m4c337ake5o", "tbnwx5g3l0zmstwf2c258r36pvpnksbt", "bbtl43drumsloilm2zfjlhj3c7v12a5d"]}}
68 | end
69 |
70 |
71 | test "#parse identity_verification_attributes" do
72 | rsp =
73 | """
74 |
75 |
76 |
77 |
78 | example.com
79 |
80 | pwCRTZ8zHIJu+vePnXEa4DJmDyGhjSS8V3TkzzL2jI8=
81 | Pending
82 |
83 |
84 |
85 | user@example.com
86 |
87 | Pending
88 |
89 |
90 |
91 |
92 |
93 | f5e3ef21-bec1-11e6-b618-27019a58dab9
94 |
95 |
96 | """
97 | |> to_success
98 |
99 | verification_attributes = %{
100 | "example.com" => %{
101 | verification_token: "pwCRTZ8zHIJu+vePnXEa4DJmDyGhjSS8V3TkzzL2jI8=",
102 | verification_status: "Pending"
103 | },
104 | "user@example.com" => %{
105 | verification_status: "Pending"
106 | }
107 | }
108 |
109 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :get_identity_verification_attributes)
110 | assert parsed_doc[:verification_attributes] == verification_attributes
111 | end
112 |
113 | test "#parse configuration_sets" do
114 | rsp =
115 | """
116 |
117 |
118 |
119 |
120 | test
121 |
122 |
123 | QUFBQUF
124 |
125 |
126 | c177d6ce-c1b0-11e6-9770-29713cf492ad
127 |
128 |
129 | """
130 | |> to_success
131 |
132 | configuration_sets = %{
133 | members: ["test"],
134 | next_token: "QUFBQUF"
135 | }
136 |
137 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :list_configuration_sets)
138 | assert parsed_doc[:configuration_sets] == configuration_sets
139 | end
140 |
141 | test "#parse send_email" do
142 | rsp =
143 | """
144 |
145 |
146 | 0100015914b22075-7a4e3573-ca72-41ce-8eda-388f81232ad9-000000
147 |
148 |
149 | 8194094b-c58a-11e6-b49d-838795cc7d3f
150 |
151 |
152 | """
153 | |> to_success
154 |
155 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :send_email)
156 |
157 | assert parsed_doc == %{
158 | request_id: "8194094b-c58a-11e6-b49d-838795cc7d3f",
159 | message_id: "0100015914b22075-7a4e3573-ca72-41ce-8eda-388f81232ad9-000000"
160 | }
161 | end
162 |
163 | test "#parse send_templated_email" do
164 | rsp =
165 | """
166 |
167 |
168 | 0100015914b22075-7a4e3573-ca72-41ce-8eda-388f81232ad9-000000
169 |
170 |
171 | 8194094b-c58a-11e6-b49d-838795cc7d3f
172 |
173 |
174 | """
175 | |> to_success
176 |
177 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :send_templated_email)
178 |
179 | assert parsed_doc == %{
180 | request_id: "8194094b-c58a-11e6-b49d-838795cc7d3f",
181 | message_id: "0100015914b22075-7a4e3573-ca72-41ce-8eda-388f81232ad9-000000"
182 | }
183 | end
184 |
185 | test "#parse send_bulk_templated_email" do
186 | rsp =
187 | """
188 |
189 |
190 |
191 |
192 | 110000663377dd66-44ffaaaa-11bb-44dd-aa77-998899883388-000000
193 | Success
194 |
195 |
196 | 110000663377dd66-778888dd-cc99-44aa-aa99-bbdd99ddff88-000000
197 | Success
198 |
199 |
200 |
201 |
202 | 22ff88dd-cc11-11ee-99bb-5599ee1144dd
203 |
204 | "
205 | """
206 | |> to_success
207 |
208 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :send_bulk_templated_email)
209 |
210 | assert parsed_doc == %{
211 | request_id: "22ff88dd-cc11-11ee-99bb-5599ee1144dd",
212 | messages: [
213 | %{message_id: "110000663377dd66-44ffaaaa-11bb-44dd-aa77-998899883388-000000", status: "Success"},
214 | %{message_id: "110000663377dd66-778888dd-cc99-44aa-aa99-bbdd99ddff88-000000", status: "Success"}
215 | ]
216 | }
217 | end
218 |
219 | test "#parse send_raw_email" do
220 | rsp =
221 | """
222 |
223 |
224 | 0101018264278cea-406a0406-5f46-412b-8f32-05ef31a62aa0-000000
225 |
226 |
227 | 3c2ddfd4-ff21-4a3d-af5f-97ec74811e22
228 |
229 |
230 | """
231 | |> to_success()
232 |
233 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :send_raw_email)
234 | assert parsed_doc == %{
235 | request_id: "3c2ddfd4-ff21-4a3d-af5f-97ec74811e22",
236 | message_id: "0101018264278cea-406a0406-5f46-412b-8f32-05ef31a62aa0-000000"
237 | }
238 | end
239 |
240 | test "#parse a delete_identity response" do
241 | rsp =
242 | """
243 |
244 |
245 |
246 | 88c79dfb-1472-11e7-94c4-4d1ecf50b91f
247 |
248 |
249 | """
250 | |> to_success
251 |
252 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :delete_identity)
253 | assert parsed_doc == %{request_id: "88c79dfb-1472-11e7-94c4-4d1ecf50b91f"}
254 | end
255 |
256 | test "#parse a set_identity_notification_topic response" do
257 | rsp =
258 | """
259 |
260 |
261 |
262 | 3d3f811a-1484-11e7-b9b1-db4762b6c4db
263 |
264 |
265 | """
266 | |> to_success
267 |
268 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :set_identity_notification_topic)
269 | assert parsed_doc == %{request_id: "3d3f811a-1484-11e7-b9b1-db4762b6c4db"}
270 | end
271 |
272 | test "#parse a set_identity_feedback_forwarding_enabled response" do
273 | rsp =
274 | """
275 |
276 |
277 |
278 | f1cc8133-149a-11e7-91a5-ed1259cbd185
279 |
280 |
281 | """
282 | |> to_success
283 |
284 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :set_identity_feedback_forwarding_enabled)
285 | assert parsed_doc == %{request_id: "f1cc8133-149a-11e7-91a5-ed1259cbd185"}
286 | end
287 |
288 | test "#parse a set_identity_headers_in_notifications_enabled response" do
289 | rsp =
290 | """
291 |
292 |
293 |
294 | 01b49b78-30ca-11e7-948a-399bafb173a2
295 |
296 | "
297 | """
298 | |> to_success
299 |
300 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :set_identity_headers_in_notifications_enabled)
301 | assert parsed_doc == %{request_id: "01b49b78-30ca-11e7-948a-399bafb173a2"}
302 | end
303 |
304 | test "#parse create_template" do
305 | rsp =
306 | """
307 |
308 |
309 |
310 | 9876defg-c666-111e-88aa-ee8833eeffaa
311 |
312 |
313 | """
314 | |> to_success
315 |
316 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :create_template)
317 | assert parsed_doc == %{request_id: "9876defg-c666-111e-88aa-ee8833eeffaa"}
318 | end
319 |
320 | test "#parse delete_template" do
321 | rsp =
322 | """
323 |
324 |
325 |
326 | 12345abcd-c666-111e-88aa-cc8899bb1177
327 |
328 |
329 | """
330 | |> to_success
331 |
332 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :delete_template)
333 | assert parsed_doc == %{request_id: "12345abcd-c666-111e-88aa-cc8899bb1177"}
334 | end
335 |
336 | test "#parse list_identities" do
337 | rsp =
338 | """
339 |
340 |
341 |
342 | user@example.com
343 | user2@example.com
344 |
345 |
346 |
347 | 12345abcd-c666-111e-88aa-cc8899bb1177
348 |
349 |
350 | """
351 | |> to_success
352 |
353 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :list_identities)
354 |
355 | assert parsed_doc == %{
356 | request_id: "12345abcd-c666-111e-88aa-cc8899bb1177",
357 | custom_verification_email_templates: %{
358 | members: ["user@example.com", "user2@example.com"],
359 | next_token: ""
360 | },
361 | identities: %{
362 | members: ["user@example.com", "user2@example.com"],
363 | next_token: ""
364 | }
365 | }
366 | end
367 |
368 | test "#parse list_custom_verification_email_templates" do
369 | rsp =
370 | """
371 |
372 |
373 |
374 |
375 | Subject
376 | https://example.com/failure
377 | https://example.com/success
378 | user@example.com
379 | Template Name
380 |
381 |
382 |
383 |
384 | 12345abcd-c666-111e-88aa-cc8899bb1177
385 |
386 |
387 | """
388 | |> to_success()
389 |
390 | {:ok, %{body: parsed_doc}} = Parsers.parse(rsp, :list_custom_verification_email_templates)
391 |
392 | assert parsed_doc == %{
393 | request_id: "12345abcd-c666-111e-88aa-cc8899bb1177",
394 | custom_verification_email_templates: %{
395 | members: [
396 | %{
397 | template_name: "Template Name",
398 | template_subject: "Subject",
399 | failure_redirection_url: "https://example.com/failure",
400 | success_redirection_url: "https://example.com/success",
401 | from_email_address: "user@example.com"
402 | }
403 | ],
404 | next_token: ""
405 | }
406 | }
407 | end
408 |
409 | test "#parse error" do
410 | rsp =
411 | """
412 |
413 |
414 | Sender
415 | MalformedInput
416 | Top level element may not be treated as a list
417 |
418 | 3ac0a9e8-bebd-11e6-9ec4-e5c47e708fa8
419 |
420 | """
421 | |> to_error
422 |
423 | {:error, {:http_error, 403, err}} = Parsers.parse(rsp, :get_identity_verification_attributes)
424 |
425 | assert "Sender" == err[:type]
426 | assert "MalformedInput" == err[:code]
427 | assert "Top level element may not be treated as a list" == err[:message]
428 | assert "3ac0a9e8-bebd-11e6-9ec4-e5c47e708fa8" == err[:request_id]
429 | end
430 | end
431 |
--------------------------------------------------------------------------------
/lib/ex_aws/ses.ex:
--------------------------------------------------------------------------------
1 | defmodule ExAws.SES do
2 | import ExAws.Utils, only: [camelize_key: 1, camelize_keys: 1]
3 |
4 | @moduledoc """
5 | Operations on AWS SES.
6 |
7 | See https://docs.aws.amazon.com/ses/latest/APIReference/Welcome.html
8 | """
9 |
10 | @notification_types [:bounce, :complaint, :delivery]
11 | @service :ses
12 | @v2_path "/v2/email"
13 |
14 | @doc """
15 | Verifies an email address.
16 | """
17 | @spec verify_email_identity(email :: binary) :: ExAws.Operation.Query.t()
18 | def verify_email_identity(email) do
19 | request(:verify_email_identity, %{"EmailAddress" => email})
20 | end
21 |
22 | @doc """
23 | Verifies a domain.
24 | """
25 | @spec verify_domain_identity(domain :: binary) :: ExAws.Operation.Query.t()
26 | def verify_domain_identity(domain) do
27 | request(:verify_domain_identity, %{"Domain" => domain})
28 | end
29 |
30 | @doc """
31 | Verifies a domain with DKIM.
32 | """
33 | @spec verify_domain_dkim(domain :: binary) :: ExAws.Operation.Query.t()
34 | def verify_domain_dkim(domain) do
35 | request(:verify_domain_dkim, %{"Domain" => domain})
36 | end
37 |
38 | @type list_identities_opt ::
39 | {:max_items, pos_integer}
40 | | {:next_token, String.t()}
41 | | {:identity_type, String.t()}
42 |
43 | @type tag :: %{Key: String.t(), Value: String.t()}
44 | @type list_topic :: %{String.t() => String.t()}
45 | @type suppression_reason :: :BOUNCE | :COMPLAINT
46 |
47 | @doc "List identities associated with the AWS account"
48 | @spec list_identities(opts :: [] | [list_identities_opt]) :: ExAws.Operation.Query.t()
49 | @deprecated "The :custom_verification_templates key will be deprecated in version 3.x.x, please use :identities instead"
50 | def list_identities(opts \\ []) do
51 | params = build_opts(opts, [:max_items, :next_token, :identity_type])
52 | request(:list_identities, params)
53 | end
54 |
55 | @doc """
56 | Fetch identities verification status and token (for domains).
57 | """
58 | @spec get_identity_verification_attributes([binary]) :: ExAws.Operation.Query.t()
59 | def get_identity_verification_attributes(identities) when is_list(identities) do
60 | params = format_member_attribute({:identities, identities})
61 | request(:get_identity_verification_attributes, params)
62 | end
63 |
64 | @type list_configuration_sets_opt ::
65 | {:max_items, pos_integer}
66 | | {:next_token, String.t()}
67 |
68 | @doc """
69 | Fetch configuration sets associated with AWS account.
70 | """
71 | @spec list_configuration_sets() :: ExAws.Operation.Query.t()
72 | @spec list_configuration_sets(opts :: [] | [list_configuration_sets_opt]) :: ExAws.Operation.Query.t()
73 | def list_configuration_sets(opts \\ []) do
74 | params = build_opts(opts, [:max_items, :next_token])
75 | request(:list_configuration_sets, params)
76 | end
77 |
78 | ## Contact lists
79 | ######################
80 |
81 | @doc """
82 | Create a contact list via the SES V2 API,
83 | see (https://docs.aws.amazon.com/ses/latest/APIReference-V2/).
84 |
85 | ## Examples
86 |
87 | ExAws.SES.create_contact_list(
88 | "Test list",
89 | "Test description",
90 | tags: [%{"Key" => "environment", "Value" => "test"}],
91 | topics: [
92 | %{
93 | "TopicName": "test_topic"
94 | "DisplayName": "Test topic",
95 | "Description": "Test discription",
96 | "DefaultSubscriptionStatus": "OPT_IN",
97 | }
98 | ]
99 | )
100 |
101 | """
102 | @type create_contact_list_opt ::
103 | {:description, String.t()}
104 | | {:tags, [tag]}
105 | | {:topics, [%{(String.t() | atom) => String.t()}]}
106 | @spec create_contact_list(String.t(), opts :: [create_contact_list_opt]) ::
107 | ExAws.Operation.JSON.t()
108 | def create_contact_list(list_name, opts \\ []) do
109 | data =
110 | prune_map(%{
111 | "ContactListName" => list_name,
112 | "Description" => opts[:description],
113 | "Tags" => opts[:tags],
114 | "Topics" => opts[:topics]
115 | })
116 |
117 | request_v2(:post, "contact-lists")
118 | |> Map.put(:data, data)
119 | end
120 |
121 | @doc """
122 | Update a contact list. Only accepts description and topic updates.
123 |
124 | ## Examples
125 |
126 | ExAws.SES.update_contact_list("test_list", description: "New description")
127 |
128 | """
129 | @type topic :: %{
130 | required(:DefaultSubscriptionStatus) => String.t(),
131 | optional(:Description) => String.t(),
132 | required(:DisplayName) => String.t(),
133 | required(:TopicName) => String.t()
134 | }
135 | @type update_contact_list_opt ::
136 | {:description, String.t()}
137 | | {:topics, [topic]}
138 | @spec update_contact_list(String.t(), opts :: [update_contact_list_opt]) :: ExAws.Operation.JSON.t()
139 | def update_contact_list(list_name, opts \\ []) do
140 | data =
141 | prune_map(%{
142 | "ContactListName" => list_name,
143 | "Description" => opts[:description],
144 | "Topics" => opts[:topics]
145 | })
146 |
147 | request_v2(:put, "contact-lists/#{list_name}")
148 | |> Map.put(:data, data)
149 | end
150 |
151 | @doc """
152 | List contact lists.
153 |
154 | The API accepts pagination parameters, but they're redundant as AWS limits
155 | usage to a single list per account.
156 | """
157 | @spec list_contact_lists() :: ExAws.Operation.JSON.t()
158 | def list_contact_lists() do
159 | request_v2(:get, "contact-lists")
160 | end
161 |
162 | @doc """
163 | Show contact list.
164 | """
165 | @spec get_contact_list(String.t()) :: ExAws.Operation.JSON.t()
166 | def get_contact_list(list_name) do
167 | request_v2(:get, "contact-lists/#{list_name}")
168 | end
169 |
170 | @doc """
171 | Delete contact list.
172 | """
173 | @spec delete_contact_list(String.t()) :: ExAws.Operation.JSON.t()
174 | def delete_contact_list(list_name) do
175 | request_v2(:delete, "contact-lists/#{list_name}")
176 | end
177 |
178 | ## Contacts
179 | ######################
180 |
181 | @doc """
182 | Create a new contact in a contact list.
183 |
184 | Options:
185 |
186 | * `:attributes` - arbitrary string to be assigned to AWS SES Contact AttributesData
187 | * `:topic_preferences` - list of maps for subscriptions to topics.
188 | SubscriptionStatus should be one of "OPT_IN" or "OPT_OUT"
189 | * `:unsubscribe_all` - causes contact to be unsubscribed from all topics
190 | """
191 | @type topic_preference :: %{
192 | TopicName: String.t(),
193 | SubscriptionStatus: String.t()
194 | }
195 | @type contact_opt ::
196 | {:attributes, String.t()}
197 | | {:topic_preferences, [topic_preference]}
198 | | {:unsubscribe_all, Boolean.t()}
199 | @spec create_contact(String.t(), email_address, [contact_opt]) :: ExAws.Operation.JSON.t()
200 | def create_contact(list_name, email, opts \\ []) do
201 | data =
202 | prune_map(%{
203 | "EmailAddress" => email,
204 | "TopicPreferences" => opts[:topic_preferences],
205 | "AttributesData" => opts[:attributes],
206 | "UnsubscribeAll" => opts[:unsubscribe_all]
207 | })
208 |
209 | request_v2(:post, "contact-lists/#{list_name}/contacts")
210 | |> Map.put(:data, data)
211 | end
212 |
213 | @doc """
214 | Update a contact in a contact list.
215 | """
216 | @spec update_contact(String.t(), email_address, [contact_opt]) :: ExAws.Operation.JSON.t()
217 | def update_contact(list_name, email, opts \\ []) do
218 | data =
219 | prune_map(%{
220 | "TopicPreferences" => opts[:topic_preferences],
221 | "AttributesData" => opts[:attributes],
222 | "UnsubscribeAll" => opts[:unsubscribe_all]
223 | })
224 |
225 | uri_encoded_email = ExAws.Request.Url.uri_encode(email)
226 |
227 | request_v2(:put, "contact-lists/#{list_name}/contacts/#{uri_encoded_email}")
228 | |> Map.put(:data, data)
229 | end
230 |
231 | @doc """
232 | Show contacts in contact list.
233 | """
234 | @spec list_contacts(String.t()) :: ExAws.Operation.JSON.t()
235 | def list_contacts(list_name) do
236 | request_v2(:get, "contact-lists/#{list_name}/contacts")
237 | end
238 |
239 | @doc """
240 | Show a contact in a contact list.
241 | """
242 | @spec get_contact(String.t(), email_address) :: ExAws.Operation.JSON.t()
243 | def get_contact(list_name, email) do
244 | uri_encoded_email = ExAws.Request.Url.uri_encode(email)
245 | request_v2(:get, "contact-lists/#{list_name}/contacts/#{uri_encoded_email}")
246 | end
247 |
248 | @doc """
249 | Delete a contact in a contact list.
250 | """
251 | @spec delete_contact(String.t(), email_address) :: ExAws.Operation.JSON.t()
252 | def delete_contact(list_name, email) do
253 | uri_encoded_email = ExAws.Request.Url.uri_encode(email)
254 | request_v2(:delete, "contact-lists/#{list_name}/contacts/#{uri_encoded_email}")
255 | end
256 |
257 | @doc """
258 | Create a bulk import job to import contacts from S3.
259 |
260 | Params:
261 |
262 | * `:import_data_source`
263 | * `:import_destination` - requires either a `ContactListDestination` or
264 | `SuppressionListDestination` map.
265 | """
266 | @type import_data_source :: %{DataFormat: String.t(), S3Url: String.t()}
267 | @type contact_list_destination :: %{
268 | ContactListImportAction: String.t(),
269 | ContactListName: String.t()
270 | }
271 | @type suppression_list_destination :: %{SuppressionListImportAction: String.t()}
272 | @type import_destination :: %{
273 | optional(:ContactListDestination) => contact_list_destination(),
274 | optional(:SuppressionListDestination) => suppression_list_destination()
275 | }
276 | @spec create_import_job(import_data_source(), import_destination()) :: ExAws.Operation.JSON.t()
277 | def create_import_job(data_source, destination) do
278 | data = %{
279 | ImportDataSource: data_source,
280 | ImportDestination: destination
281 | }
282 |
283 | request_v2(:post, "import-jobs")
284 | |> Map.put(:data, data)
285 | end
286 |
287 | ## Suppression Lists
288 | ######################
289 | @doc """
290 | Add an email address to list of suppressed destinations. A suppression reason
291 | is mandatory (see `t:suppression_reason()`).
292 | """
293 | @spec put_suppressed_destination(String.t(), SuppressionReason.t()) :: ExAws.Operation.JSON.t()
294 | def put_suppressed_destination(email_address, suppression_reason) do
295 | request_v2(:put, "suppression/addresses")
296 | |> Map.put(:data, %{
297 | EmailAddress: email_address,
298 | Reason: suppression_reason
299 | })
300 | end
301 |
302 | @doc """
303 | Delete an email address from list of suppressed destinations.
304 | """
305 | @spec delete_suppressed_destination(String.t()) :: ExAws.Operation.JSON.t()
306 | def delete_suppressed_destination(email_address) do
307 | uri_encoded_email_address = ExAws.Request.Url.uri_encode(email_address)
308 |
309 | request_v2(:delete, "suppression/addresses/#{uri_encoded_email_address}")
310 | end
311 |
312 | ## Templates
313 | ######################
314 |
315 | @doc "Get email template"
316 | @spec get_template(String.t()) :: ExAws.Operation.Query.t()
317 | def get_template(template_name) do
318 | request(:get_template, %{"TemplateName" => template_name})
319 | end
320 |
321 | @doc """
322 | Get an email templates via V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetEmailTemplate.html
323 | """
324 | @spec get_email_template(String.t()) :: ExAws.Operation.JSON.t()
325 | def get_email_template(template_name) do
326 | request_v2(:get, "templates/#{template_name}")
327 | end
328 |
329 | @type list_templates_opt ::
330 | {:max_items, pos_integer}
331 | | {:next_token, String.t()}
332 |
333 | @doc """
334 | List email templates.
335 | """
336 | @spec list_templates(opts :: [] | [list_templates_opt]) :: ExAws.Operation.Query.t()
337 | def list_templates(opts \\ []) do
338 | params = build_opts(opts, [:max_items, :next_token])
339 | request(:list_templates, params)
340 | end
341 |
342 | @doc """
343 | List email templates via V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListEmailTemplate.html
344 | """
345 | @type list_templates_opt_v2 ::
346 | {:page_size, pos_integer}
347 | | {:next_token, String.t()}
348 | @spec list_email_templates(opts :: [] | [list_templates_opt_v2]) :: ExAws.Operation.JSON.t()
349 | def list_email_templates(opts \\ []) do
350 | params = build_opts(opts, [:page_size, :next_token])
351 | request_v2(:get, "templates?#{URI.encode_query(params)}")
352 | end
353 |
354 | @doc """
355 | Creates an email template.
356 | """
357 | @type create_template_opt :: {:configuration_set_name, String.t()}
358 | @spec create_template(String.t(), String.t(), String.t(), String.t(), opts :: [create_template_opt]) ::
359 | ExAws.Operation.Query.t()
360 | def create_template(template_name, subject, html, text, opts \\ []) do
361 | template =
362 | %{
363 | "TemplateName" => template_name,
364 | "SubjectPart" => subject
365 | }
366 | |> put_if_not_nil("HtmlPart", html)
367 | |> put_if_not_nil("TextPart", text)
368 | |> flatten_attrs("Template")
369 |
370 | params =
371 | opts
372 | |> build_opts([:configuration_set_name])
373 | |> Map.merge(template)
374 |
375 | request(:create_template, params)
376 | end
377 |
378 | @doc """
379 | Create an email template via V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailTemplate.html
380 | """
381 | @spec create_email_template(String.t(), String.t(), String.t(), String.t()) :: ExAws.Operation.JSON.t()
382 | def create_email_template(template_name, subject, html, text) do
383 | template_content =
384 | %{
385 | "Subject" => subject
386 | }
387 | |> put_if_not_nil("Html", html)
388 | |> put_if_not_nil("Text", text)
389 |
390 | params =
391 | %{
392 | "TemplateName" => template_name,
393 | "TemplateContent" => template_content
394 | }
395 |
396 | request_v2(:post, "templates")
397 | |> Map.put(:data, params)
398 | end
399 |
400 | @doc """
401 | Updates an email template.
402 | """
403 | @type update_template_opt :: {:configuration_set_name, String.t()}
404 | @spec update_template(String.t(), String.t(), String.t(), String.t(), opts :: [update_template_opt]) ::
405 | ExAws.Operation.Query.t()
406 | def update_template(template_name, subject, html, text, opts \\ []) do
407 | template =
408 | %{
409 | "TemplateName" => template_name,
410 | "SubjectPart" => subject
411 | }
412 | |> put_if_not_nil("HtmlPart", html)
413 | |> put_if_not_nil("TextPart", text)
414 | |> flatten_attrs("Template")
415 |
416 | params =
417 | opts
418 | |> build_opts([:configuration_set_name])
419 | |> Map.merge(template)
420 |
421 | request(:update_template, params)
422 | end
423 |
424 | @doc """
425 | Update an email template via V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_UpdateEmailTemplate.html
426 | """
427 | @spec update_email_template(String.t(), String.t(), String.t(), String.t()) :: ExAws.Operation.JSON.t()
428 | def update_email_template(template_name, subject, html, text) do
429 | template_content =
430 | %{
431 | "Subject" => subject
432 | }
433 | |> put_if_not_nil("Html", html)
434 | |> put_if_not_nil("Text", text)
435 |
436 | params = %{"TemplateContent" => template_content}
437 |
438 | request_v2(:put, "templates/#{template_name}")
439 | |> Map.put(:data, params)
440 | end
441 |
442 | @doc """
443 | Deletes an email template.
444 | """
445 | @spec delete_template(binary) :: ExAws.Operation.Query.t()
446 | def delete_template(template_name) do
447 | params = %{
448 | "TemplateName" => template_name
449 | }
450 |
451 | request(:delete_template, params)
452 | end
453 |
454 | @doc """
455 | Delete an email template via V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteEmailTemplate.html
456 | """
457 | @spec delete_email_template(binary) :: ExAws.Operation.JSON.t()
458 | def delete_email_template(template_name) do
459 | request_v2(:delete, "templates/#{template_name}")
460 | end
461 |
462 | ## Emails
463 | ######################
464 |
465 | @type email_address :: binary
466 |
467 | @type message :: %{
468 | body: %{html: %{data: binary, charset: binary}, text: %{data: binary, charset: binary}},
469 | subject: %{data: binary, charset: binary}
470 | }
471 | @type destination :: %{to: [email_address], cc: [email_address], bcc: [email_address]}
472 |
473 | @type bulk_destination :: [%{destination: destination, replacement_template_data: binary}]
474 |
475 | @type send_email_opt ::
476 | {:configuration_set_name, String.t()}
477 | | {:reply_to, [email_address]}
478 | | {:return_path, String.t()}
479 | | {:return_path_arn, String.t()}
480 | | {:source, String.t()}
481 | | {:source_arn, String.t()}
482 | | {:tags, %{(String.t() | atom) => String.t()}}
483 |
484 | @doc """
485 | Composes an email message.
486 | """
487 | @spec send_email(dst :: destination, msg :: message, src :: binary) :: ExAws.Operation.Query.t()
488 | @spec send_email(dst :: destination, msg :: message, src :: binary, opts :: [send_email_opt]) ::
489 | ExAws.Operation.Query.t()
490 | def send_email(dst, msg, src, opts \\ []) do
491 | params =
492 | opts
493 | |> build_opts([:configuration_set_name, :return_path, :return_path_arn, :source_arn, :bcc])
494 | |> Map.merge(format_member_attribute(:reply_to_addresses, opts[:reply_to]))
495 | |> Map.merge(flatten_attrs(msg, "message"))
496 | |> Map.merge(format_tags(opts[:tags]))
497 | |> Map.merge(format_dst(dst))
498 | |> Map.put_new("Source", src)
499 |
500 | request(:send_email, params)
501 | end
502 |
503 | @doc """
504 | Send an email via the SES V2 API, which supports list management.
505 |
506 | `:content` should include one of a `Raw`, `Simple`, or `Template` key.
507 | """
508 | @type destination_v2 :: %{
509 | optional(:ToAddresses) => [email_address],
510 | optional(:CcAddresses) => [email_address],
511 | optional(:BccAddresses) => [email_address]
512 | }
513 | @type email_field :: %{optional(:Charset) => String.t(), required(:Data) => String.t()}
514 | @type email_content :: %{
515 | optional(:Raw) => %{Data: binary},
516 | optional(:Simple) => %{Body: %{Html: email_field, Text: email_field}, Subject: email_field},
517 | optional(:Template) => %{TemplateArn: String.t(), TemplateData: String.t(), TemplateName: String.t()}
518 | }
519 | @type(
520 | send_email_v2_opt ::
521 | {:configuration_set_name, String.t()}
522 | | {:tags, [tag]}
523 | | {:feedback_forwarding_address, String.t()}
524 | | {:feedback_forwarding_arn, String.t()}
525 | | {:from_arn, String.t()}
526 | | {:list_management, %{ContactListName: String.t(), TopicName: String.t()}}
527 | | {:reply_addresses, [String.t()]},
528 | @spec(send_email_v2(destination_v2, email_content, email_address, [send_email_v2_opt]))
529 | )
530 | def send_email_v2(destination, content, from_email, opts \\ []) do
531 | data =
532 | prune_map(%{
533 | ConfigurationSetName: opts[:configuration_set_name],
534 | Content: content,
535 | Destination: destination,
536 | EmailTags: opts[:tags],
537 | FeedbackForwardingEmailAddress: opts[:feedback_forwarding_address],
538 | FeedbackForwardingEmailAddressIdentityArn: opts[:feedback_forwarding_arn],
539 | FromEmailAddress: from_email,
540 | FromEmailAddressIdentityArn: opts[:from_arn],
541 | ListManagementOptions: opts[:list_management],
542 | ReplyToAddresses: opts[:reply_addresses]
543 | })
544 |
545 | request_v2(:post, "outbound-emails")
546 | |> Map.put(:data, data)
547 | end
548 |
549 | @doc """
550 | Send a raw Email.
551 | """
552 | @type send_raw_email_opt ::
553 | {:configuration_set_name, String.t()}
554 | | {:from_arn, String.t()}
555 | | {:return_path_arn, String.t()}
556 | | {:source, String.t()}
557 | | {:source_arn, String.t()}
558 | | {:tags, %{(String.t() | atom) => String.t()}}
559 |
560 | @spec send_raw_email(binary, opts :: [send_raw_email_opt]) :: ExAws.Operation.Query.t()
561 | def send_raw_email(raw_msg, opts \\ []) do
562 | params =
563 | opts
564 | |> build_opts([:configuration_set_name, :from_arn, :return_path_arn, :source, :source_arn])
565 | |> Map.merge(format_tags(opts[:tags]))
566 | |> Map.put("RawMessage.Data", Base.encode64(raw_msg))
567 |
568 | request(:send_raw_email, params)
569 | end
570 |
571 | @doc """
572 | Send a templated Email.
573 | """
574 | @type send_templated_email_opt ::
575 | {:configuration_set_name, String.t()}
576 | | {:return_path, String.t()}
577 | | {:return_path_arn, String.t()}
578 | | {:source, String.t()}
579 | | {:source_arn, String.t()}
580 | | {:reply_to, [email_address]}
581 | | {:tags, %{(String.t() | atom) => String.t()}}
582 |
583 | @spec send_templated_email(
584 | dst :: destination,
585 | src :: binary,
586 | template :: binary,
587 | template_data :: map,
588 | opts :: [send_templated_email_opt]
589 | ) :: ExAws.Operation.Query.t()
590 | def send_templated_email(dst, src, template, template_data, opts \\ []) do
591 | params =
592 | opts
593 | |> build_opts([:configuration_set_name, :return_path, :return_path_arn, :source_arn, :bcc])
594 | |> Map.merge(format_member_attribute(:reply_to_addresses, opts[:reply_to]))
595 | |> Map.merge(format_tags(opts[:tags]))
596 | |> Map.merge(format_dst(dst))
597 | |> Map.put("Source", src)
598 | |> Map.put("Template", template)
599 | |> Map.put("TemplateData", format_template_data(template_data))
600 |
601 | request(:send_templated_email, params)
602 | end
603 |
604 | @doc """
605 | Send a templated email to multiple destinations.
606 | """
607 | @type send_bulk_templated_email_opt ::
608 | {:configuration_set_name, String.t()}
609 | | {:return_path, String.t()}
610 | | {:return_path_arn, String.t()}
611 | | {:source_arn, String.t()}
612 | | {:default_template_data, String.t()}
613 | | {:reply_to, [email_address]}
614 | | {:tags, %{(String.t() | atom) => String.t()}}
615 |
616 | @spec send_bulk_templated_email(
617 | template :: binary,
618 | source :: binary,
619 | destinations :: bulk_destination,
620 | opts :: [send_bulk_templated_email_opt]
621 | ) :: ExAws.Operation.Query.t()
622 | def send_bulk_templated_email(template, source, destinations, opts \\ []) do
623 | params =
624 | opts
625 | |> build_opts([:configuration_set_name, :return_path, :return_path_arn, :source_arn, :default_template_data])
626 | |> Map.merge(format_member_attribute(:reply_to_addresses, opts[:reply_to]))
627 | |> Map.merge(format_tags(opts[:tags]))
628 | |> Map.merge(format_bulk_destinations(destinations))
629 | |> Map.put("DefaultTemplateData", format_template_data(opts[:default_template_data]))
630 | |> Map.put("Source", source)
631 | |> Map.put("Template", template)
632 |
633 | request(:send_bulk_templated_email, params)
634 | end
635 |
636 | @doc """
637 | Deletes the specified identity (an email address or a domain) from the list
638 | of verified identities.
639 | """
640 | @spec delete_identity(binary) :: ExAws.Operation.Query.t()
641 | def delete_identity(identity) do
642 | request(:delete_identity, %{"Identity" => identity})
643 | end
644 |
645 | @type set_identity_notification_topic_opt :: {:sns_topic, binary}
646 | @type notification_type :: :bounce | :complaint | :delivery
647 |
648 | @doc """
649 | Sets the Amazon Simple Notification Service (Amazon SNS) topic to which
650 | Amazon SES will publish delivery notifications for emails sent with given
651 | identity.
652 |
653 | Absent `:sns_topic` options cleans SnsTopic and disables publishing.
654 |
655 | Notification type can be on of the `:bounce`, `:complaint`, or `:delivery`.
656 | Requests are throttled to one per second.
657 | """
658 | @spec set_identity_notification_topic(binary, notification_type, set_identity_notification_topic_opt | []) ::
659 | ExAws.Operation.Query.t()
660 | def set_identity_notification_topic(identity, type, opts \\ []) when type in @notification_types do
661 | notification_type = Atom.to_string(type) |> String.capitalize()
662 |
663 | params =
664 | opts
665 | |> build_opts([:sns_topic])
666 | |> Map.merge(%{"Identity" => identity, "NotificationType" => notification_type})
667 |
668 | request(:set_identity_notification_topic, params)
669 | end
670 |
671 | @doc """
672 | Enables or disables whether Amazon SES forwards notifications as email.
673 | """
674 | @spec set_identity_feedback_forwarding_enabled(boolean, binary) :: ExAws.Operation.Query.t()
675 | def set_identity_feedback_forwarding_enabled(enabled, identity) do
676 | request(:set_identity_feedback_forwarding_enabled, %{"ForwardingEnabled" => enabled, "Identity" => identity})
677 | end
678 |
679 | @doc """
680 | Build message object.
681 | """
682 | @spec build_message(binary, binary, binary, binary) :: message
683 | def build_message(html, txt, subject, charset \\ "UTF-8") do
684 | %{
685 | body: %{
686 | html: %{data: html, charset: charset},
687 | text: %{data: txt, charset: charset}
688 | },
689 | subject: %{data: subject, charset: charset}
690 | }
691 | end
692 |
693 | @doc """
694 | Set whether SNS notifications should include original email headers or not.
695 | """
696 | @spec set_identity_headers_in_notifications_enabled(binary, notification_type, boolean) :: ExAws.Operation.Query.t()
697 | def set_identity_headers_in_notifications_enabled(identity, type, enabled) do
698 | notification_type = Atom.to_string(type) |> String.capitalize()
699 |
700 | request(
701 | :set_identity_headers_in_notifications_enabled,
702 | %{"Identity" => identity, "NotificationType" => notification_type, "Enabled" => enabled}
703 | )
704 | end
705 |
706 | @doc "Create a custom verification email template."
707 | @spec create_custom_verification_email_template(
708 | String.t(),
709 | String.t(),
710 | String.t(),
711 | String.t(),
712 | String.t(),
713 | String.t()
714 | ) :: ExAws.Operation.Query.t()
715 | def create_custom_verification_email_template(
716 | template_name,
717 | from_email_address,
718 | template_subject,
719 | template_content,
720 | success_redirection_url,
721 | failure_redirection_url
722 | ) do
723 | request(:create_custom_verification_email_template, %{
724 | "TemplateName" => template_name,
725 | "FromEmailAddress" => from_email_address,
726 | "TemplateSubject" => template_subject,
727 | "TemplateContent" => template_content,
728 | "SuccessRedirectionURL" => success_redirection_url,
729 | "FailureRedirectionURL" => failure_redirection_url
730 | })
731 | end
732 |
733 | @type update_custom_verification_email_template_opt ::
734 | {:template_name, String.t()}
735 | | {:from_email_address, String.t()}
736 | | {:template_subject, String.t()}
737 | | {:template_content, String.t()}
738 | | {:success_redirection_url, String.t()}
739 | | {:failure_redirection_url, String.t()}
740 | @doc "Update or create a custom verification email template."
741 | @spec update_custom_verification_email_template(opts :: [update_custom_verification_email_template_opt] | []) ::
742 | ExAws.Operation.Query.t()
743 | def update_custom_verification_email_template(opts \\ []) do
744 | params =
745 | opts
746 | |> build_opts([
747 | :template_name,
748 | :from_email_address,
749 | :template_subject,
750 | :template_content
751 | ])
752 | |> maybe_put_param(opts, :success_redirection_url, "SuccessRedirectionURL")
753 | |> maybe_put_param(opts, :failure_redirection_url, "FailureRedirectionURL")
754 |
755 | request(:update_custom_verification_email_template, params)
756 | end
757 |
758 | @doc "Delete custom verification email template."
759 | @spec delete_custom_verification_email_template(String.t()) :: ExAws.Operation.Query.t()
760 | def delete_custom_verification_email_template(template_name) do
761 | request(:delete_custom_verification_email_template, %{"TemplateName" => template_name})
762 | end
763 |
764 | @type list_custom_verification_email_templates_opt :: {:max_results, String.t()} | {:next_token, String.t()}
765 | @doc "Lists custom verification email templates."
766 | @spec list_custom_verification_email_templates(opts :: [list_custom_verification_email_templates_opt()] | []) ::
767 | ExAws.Operation.Query.t()
768 | def list_custom_verification_email_templates(opts \\ []) do
769 | params = build_opts(opts, [:max_results, :next_token])
770 | request(:list_custom_verification_email_templates, params)
771 | end
772 |
773 | @type send_custom_verification_email_opt :: {:configuration_set_name, String.t()}
774 | @doc "Send a verification email using a custom template."
775 | @spec send_custom_verification_email(String.t(), String.t(), opts :: [send_custom_verification_email_opt] | []) ::
776 | ExAws.Operation.Query.t()
777 | def send_custom_verification_email(email_address, template_name, opts \\ []) do
778 | params =
779 | opts
780 | |> build_opts([:configuration_set_name])
781 | |> Map.put("EmailAddress", email_address)
782 | |> Map.put("TemplateName", template_name)
783 |
784 | request(:send_custom_verification_email, params)
785 | end
786 |
787 | @doc "Create a custom verification email template via the SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateCustomVerificationEmailTemplate.html"
788 | @spec create_custom_verification_email_template_v2(
789 | String.t(),
790 | String.t(),
791 | String.t(),
792 | String.t(),
793 | String.t(),
794 | String.t()
795 | ) :: ExAws.Operation.JSON.t()
796 | def create_custom_verification_email_template_v2(
797 | template_name,
798 | from_email_address,
799 | template_subject,
800 | template_content,
801 | success_redirection_url,
802 | failure_redirection_url
803 | ) do
804 | request_v2(:post, "/custom-verification-email-templates")
805 | |> Map.put(:data, %{
806 | "TemplateName" => template_name,
807 | "FromEmailAddress" => from_email_address,
808 | "TemplateSubject" => template_subject,
809 | "TemplateContent" => template_content,
810 | "SuccessRedirectionURL" => success_redirection_url,
811 | "FailureRedirectionURL" => failure_redirection_url
812 | })
813 | end
814 |
815 | @doc "Update an existing custom verification email template via the SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_UpdateCustomVerificationEmailTemplate.html"
816 | @spec update_custom_verification_email_template_v2(
817 | String.t(),
818 | opts :: [update_custom_verification_email_template_opt] | []
819 | ) ::
820 | ExAws.Operation.JSON.t()
821 | def update_custom_verification_email_template_v2(template_name, opts \\ []) do
822 | params =
823 | opts
824 | |> build_opts([
825 | :from_email_address,
826 | :template_subject,
827 | :template_content
828 | ])
829 | |> maybe_put_param(opts, :success_redirection_url, "SuccessRedirectionURL")
830 | |> maybe_put_param(opts, :failure_redirection_url, "FailureRedirectionURL")
831 |
832 | request_v2(:put, "custom-verification-email-templates/#{template_name}")
833 | |> Map.put(:data, params)
834 | end
835 |
836 | @doc "Deletes an existing custom verification email template via the SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteCustomVerificationEmailTemplate.html"
837 | @spec delete_custom_verification_email_template_v2(String.t()) :: ExAws.Operation.JSON.t()
838 | def delete_custom_verification_email_template_v2(template_name) do
839 | request_v2(:delete, "custom-verification-email-templates/#{template_name}")
840 | end
841 |
842 | @type list_custom_verification_email_templates_opt_v2 :: {:page_size, pos_integer} | {:next_token, String.t()}
843 | @doc "Lists the existing custom verification email templates via the SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListCustomVerificationEmailTemplates.html"
844 | @spec list_custom_verification_email_templates_v2(opts :: [list_custom_verification_email_templates_opt_v2()] | []) ::
845 | ExAws.Operation.JSON.t()
846 | def list_custom_verification_email_templates_v2(opts \\ []) do
847 | params = build_opts(opts, [:page_size, :next_token])
848 | request_v2(:get, "custom-verification-email-templates?#{URI.encode_query(params)}")
849 | end
850 |
851 | @doc "Get a custom verification email template via the SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetCustomVerificationEmailTemplate.html"
852 | @spec get_custom_verification_email_template_v2(String.t()) :: ExAws.Operation.JSON.t()
853 | def get_custom_verification_email_template_v2(template_name) do
854 | request_v2(:get, "custom-verification-email-templates/#{template_name}")
855 | end
856 |
857 | @doc "Send a verification email using a custom template via SES V2 API. See https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendCustomVerificationEmail.html"
858 | @spec send_custom_verification_email_v2(String.t(), String.t(), opts :: [send_custom_verification_email_opt] | []) ::
859 | ExAws.Operation.JSON.t()
860 | def send_custom_verification_email_v2(email_address, template_name, opts \\ []) do
861 | params =
862 | opts
863 | |> build_opts([:configuration_set_name])
864 | |> Map.put("EmailAddress", email_address)
865 | |> Map.put("TemplateName", template_name)
866 |
867 | request_v2(:post, "outbound-custom-verification-emails")
868 | |> Map.put(:data, params)
869 | end
870 |
871 | @spec test_render_email_template(String.t(), map()) :: ExAws.Operation.JSON.t()
872 | def test_render_email_template(template_name, template_data) do
873 | request_v2(:post, "templates/#{template_name}/render")
874 | |> Map.put(:data, %{
875 | "TemplateData" => format_template_data(template_data)
876 | })
877 | end
878 |
879 | ## Receipt Rules and Rule Sets
880 | ######################
881 | @doc "Describe the given receipt rule set."
882 | @spec describe_receipt_rule_set(String.t()) :: ExAws.Operation.Query.t()
883 | def describe_receipt_rule_set(rule_set_name) do
884 | request(:describe_receipt_rule_set, %{"RuleSetName" => rule_set_name})
885 | end
886 |
887 | defp format_dst(dst, root \\ "destination") do
888 | dst =
889 | Enum.reduce([:to, :bcc, :cc], %{}, fn key, acc ->
890 | case Map.fetch(dst, key) do
891 | {:ok, val} -> Map.put(acc, :"#{key}_addresses", val)
892 | _ -> acc
893 | end
894 | end)
895 |
896 | dst
897 | |> Map.to_list()
898 | |> format_member_attributes([:bcc_addresses, :cc_addresses, :to_addresses])
899 | |> flatten_attrs(root)
900 | end
901 |
902 | defp format_template_data(nil), do: "{}"
903 |
904 | defp format_template_data(template_data), do: Map.get(aws_base_config(), :json_codec).encode!(template_data)
905 |
906 | defp format_bulk_destinations(destinations) do
907 | destinations
908 | |> Enum.with_index(1)
909 | |> Enum.flat_map(fn
910 | {%{destination: destination} = destination_member, index} ->
911 | root = "Destinations.member.#{index}"
912 |
913 | destination
914 | |> format_dst("#{root}.Destination")
915 | |> add_replacement_template_data(destination_member, root)
916 | |> Map.to_list()
917 | end)
918 | |> Map.new()
919 | end
920 |
921 | defp add_replacement_template_data(destination, %{replacement_template_data: replacement_template_data}, root) do
922 | destination
923 | |> Map.put("#{root}.ReplacementTemplateData", format_template_data(replacement_template_data))
924 | end
925 |
926 | defp add_replacement_template_data(destination, _, _), do: destination
927 |
928 | defp format_tags(nil), do: %{}
929 |
930 | defp format_tags(tags) do
931 | tags
932 | |> Enum.with_index(1)
933 | |> Enum.reduce(%{}, fn {tag, index}, acc ->
934 | key = camelize_key("tags.member.#{index}")
935 | Map.merge(acc, flatten_attrs(tag, key))
936 | end)
937 | end
938 |
939 | ## Request
940 | ######################
941 |
942 | defp request(action, params) do
943 | action_string = action |> Atom.to_string() |> Macro.camelize()
944 |
945 | %ExAws.Operation.Query{
946 | path: "/",
947 | params: params |> Map.put("Action", action_string),
948 | service: @service,
949 | action: action,
950 | parser: &ExAws.SES.Parsers.parse/2
951 | }
952 | end
953 |
954 | defp request_v2(method) do
955 | %ExAws.Operation.JSON{
956 | http_method: method,
957 | path: @v2_path,
958 | service: @service
959 | }
960 | end
961 |
962 | defp request_v2(method, resource) do
963 | request_v2(method)
964 | |> Map.put(:path, @v2_path <> "/#{resource}")
965 | end
966 |
967 | defp build_opts(opts, permitted) do
968 | opts
969 | |> Map.new()
970 | |> Map.take(permitted)
971 | |> camelize_keys
972 | end
973 |
974 | defp maybe_put_param(params, opts, key, name) do
975 | case opts[key] do
976 | nil -> params
977 | value -> Map.put(params, name, value)
978 | end
979 | end
980 |
981 | defp format_member_attributes(opts, members) do
982 | opts
983 | |> Map.new()
984 | |> Map.take(members)
985 | |> Enum.reduce(Map.new(opts), fn entry, acc -> Map.merge(acc, format_member_attribute(entry)) end)
986 | |> Map.drop(members)
987 | end
988 |
989 | defp format_member_attribute(key, collection), do: format_member_attribute({key, collection})
990 |
991 | defp format_member_attribute({_, nil}), do: %{}
992 |
993 | defp format_member_attribute({key, collection}) do
994 | collection
995 | |> Enum.with_index(1)
996 | |> Map.new(fn {item, index} ->
997 | {"#{camelize_key(key)}.member.#{index}", item}
998 | end)
999 | end
1000 |
1001 | defp flatten_attrs(attrs, root) do
1002 | do_flatten_attrs({attrs, camelize_key(root)})
1003 | |> List.flatten()
1004 | |> Map.new()
1005 | end
1006 |
1007 | defp do_flatten_attrs({attrs, root}) when is_map(attrs) do
1008 | Enum.map(attrs, fn {k, v} ->
1009 | do_flatten_attrs({v, root <> "." <> camelize_key(k)})
1010 | end)
1011 | end
1012 |
1013 | defp do_flatten_attrs({val, path}) do
1014 | {camelize_key(path), val}
1015 | end
1016 |
1017 | defp put_if_not_nil(map, _, nil), do: map
1018 | defp put_if_not_nil(map, key, value), do: map |> Map.put(key, value)
1019 |
1020 | defp aws_base_config(), do: ExAws.Config.build_base(@service)
1021 |
1022 | defp prune_map(map) do
1023 | map
1024 | |> Enum.reject(fn {_k, v} -> is_nil(v) end)
1025 | |> Map.new()
1026 | end
1027 | end
1028 |
--------------------------------------------------------------------------------
/test/lib/ses_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ExAws.SESTest do
2 | use ExUnit.Case, async: true
3 | alias ExAws.SES
4 |
5 | @list_name "test_list"
6 |
7 | setup_all do
8 | {:ok,
9 | email: "user@example.com",
10 | domain: "example.com",
11 | tag: %{Key: "environment", Value: "test"},
12 | topic: %{TopicName: "test_topic", SubscriptionStatus: "OPT_IN"},
13 | list_management: %{ContactListName: @list_name, TopicName: "test_topic"},
14 | destination: %{
15 | ToAddresses: ["test1@example.com"],
16 | CcAddresses: ["test2@example.com"],
17 | BccAddresses: ["test3@example.com"]
18 | }}
19 | end
20 |
21 | test "#verify_email_identity", ctx do
22 | expected = %{"Action" => "VerifyEmailIdentity", "EmailAddress" => ctx.email}
23 | assert expected == SES.verify_email_identity(ctx.email).params
24 | end
25 |
26 | test "#verify_domain_identity", ctx do
27 | expected = %{"Action" => "VerifyDomainIdentity", "Domain" => ctx.domain}
28 | assert expected == SES.verify_domain_identity(ctx.domain).params
29 | end
30 |
31 | test "#verify_domain_dkim", ctx do
32 | expected = %{"Action" => "VerifyDomainDkim", "Domain" => ctx.domain}
33 | assert expected == SES.verify_domain_dkim(ctx.domain).params
34 | end
35 |
36 | @tag :integration
37 | test "#verify_email_identity request" do
38 | resp = SES.verify_email_identity("success@simulator.amazonses.com") |> ExAws.request()
39 | assert {:ok, %{body: %{request_id: _}}} = resp
40 | end
41 |
42 | @tag :integration
43 | test "#verify_domain_identity request" do
44 | resp = SES.verify_domain_identity("simulator.amazonses.com") |> ExAws.request()
45 | assert {:ok, %{body: %{request_id: _}}} = resp
46 | end
47 |
48 | @tag :integration
49 | test "#verify_domain_dkimrequest" do
50 | resp = SES.verify_domain_dkim("simulator.amazonses.com") |> ExAws.request()
51 | assert {:ok, %{body: %{request_id: _}}} = resp
52 | end
53 |
54 | test "#identity_verification_attributes", ctx do
55 | expected = %{
56 | "Action" => "GetIdentityVerificationAttributes",
57 | "Identities.member.1" => ctx.email
58 | }
59 |
60 | assert expected == SES.get_identity_verification_attributes([ctx.email]).params
61 | end
62 |
63 | test "#configuration_sets" do
64 | expected = %{"Action" => "ListConfigurationSets", "MaxItems" => 1, "NextToken" => "QUFBQUF"}
65 | assert expected == SES.list_configuration_sets(max_items: 1, next_token: "QUFBQUF").params
66 | end
67 |
68 | test "#list_identities" do
69 | expected = %{"Action" => "ListIdentities", "MaxItems" => 1, "NextToken" => "QUFBQUF", "IdentityType" => "Domain"}
70 | assert expected == SES.list_identities(max_items: 1, next_token: "QUFBQUF", identity_type: "Domain").params
71 |
72 | expected = %{"Action" => "ListIdentities", "MaxItems" => 1, "NextToken" => "QUFBQUF"}
73 | assert expected == SES.list_identities(max_items: 1, next_token: "QUFBQUF").params
74 | end
75 |
76 | describe "contact lists" do
77 | test "#create_contact_list" do
78 | tag = %{Key: "environment", Value: "test"}
79 |
80 | expected_data = %{
81 | "ContactListName" => @list_name,
82 | "Tags" => [%{Key: "environment", Value: "test"}]
83 | }
84 |
85 | operation = SES.create_contact_list(@list_name, tags: [tag])
86 |
87 | assert operation.http_method == :post
88 | assert operation.path == "/v2/email/contact-lists"
89 | assert operation.data == expected_data
90 | end
91 |
92 | test "#update_contact_list" do
93 | description = "test description"
94 | topic = %{TopicName: "test_topic", DisplayName: "test", DefaultSubscriptionStatus: "OPT_OUT"}
95 |
96 | expected_data = %{
97 | "ContactListName" => @list_name,
98 | "Description" => description,
99 | "Topics" => [topic]
100 | }
101 |
102 | operation = SES.update_contact_list(@list_name, description: description, topics: [topic])
103 |
104 | assert operation.http_method == :put
105 | assert operation.path == "/v2/email/contact-lists/#{@list_name}"
106 | assert operation.data == expected_data
107 | end
108 |
109 | test "#list_contact_lists" do
110 | operation = SES.list_contact_lists()
111 |
112 | assert operation.http_method == :get
113 | assert operation.path == "/v2/email/contact-lists"
114 | end
115 |
116 | test "#get_contact_list" do
117 | operation = SES.get_contact_list(@list_name)
118 |
119 | assert operation.http_method == :get
120 | assert operation.path == "/v2/email/contact-lists/#{@list_name}"
121 | end
122 |
123 | test "#delete_contact_list" do
124 | operation = SES.delete_contact_list(@list_name)
125 |
126 | assert operation.http_method == :delete
127 | assert operation.path == "/v2/email/contact-lists/#{@list_name}"
128 | end
129 |
130 | test "#create_import_job" do
131 | source = %{DataFormat: "CSV", S3Url: "s3://test_bucket/test_object.csv"}
132 | destination = %{ContactListDestination: %{ContactListImportAction: "PUT", ContactListName: @list_name}}
133 |
134 | expected_data = %{
135 | ImportDataSource: source,
136 | ImportDestination: destination
137 | }
138 |
139 | operation = SES.create_import_job(source, destination)
140 |
141 | assert operation.http_method == :post
142 | assert operation.path == "/v2/email/import-jobs"
143 | assert operation.data == expected_data
144 | end
145 | end
146 |
147 | describe "contacts" do
148 | test "#create_contact" do
149 | email = "test@example.com"
150 | topic = %{TopicName: "test_topic", SubscriptionStatus: "OPT_IN"}
151 | attributes = "test attribute"
152 | unsubscribe = false
153 |
154 | expected_data = %{
155 | "EmailAddress" => email,
156 | "TopicPreferences" => [topic],
157 | "AttributesData" => attributes,
158 | "UnsubscribeAll" => unsubscribe
159 | }
160 |
161 | operation =
162 | SES.create_contact(
163 | @list_name,
164 | email,
165 | attributes: attributes,
166 | topic_preferences: [topic],
167 | unsubscribe_all: unsubscribe
168 | )
169 |
170 | assert operation.http_method == :post
171 | assert operation.path == "/v2/email/contact-lists/#{@list_name}/contacts"
172 | assert operation.data == expected_data
173 | end
174 |
175 | test "#update_contact" do
176 | email = "test+bar@example.com"
177 | topic = %{TopicName: "test_topic", SubscriptionStatus: "OPT_IN"}
178 | attributes = "test attribute"
179 | unsubscribe = false
180 |
181 | expected_data = %{
182 | "TopicPreferences" => [topic],
183 | "AttributesData" => attributes,
184 | "UnsubscribeAll" => unsubscribe
185 | }
186 |
187 | operation =
188 | SES.update_contact(
189 | @list_name,
190 | email,
191 | attributes: attributes,
192 | topic_preferences: [topic],
193 | unsubscribe_all: unsubscribe
194 | )
195 |
196 | assert operation.http_method == :put
197 | assert operation.path == "/v2/email/contact-lists/#{@list_name}/contacts/test%2Bbar%40example.com"
198 | assert operation.data == expected_data
199 | end
200 |
201 | test "#list_contacts" do
202 | operation = SES.list_contacts(@list_name)
203 |
204 | assert operation.http_method == :get
205 | assert operation.path == "/v2/email/contact-lists/#{@list_name}/contacts"
206 | end
207 |
208 | test "#get_contact" do
209 | email = "test+bar@example.com"
210 | operation = SES.get_contact(@list_name, email)
211 |
212 | assert operation.http_method == :get
213 | assert operation.path == "/v2/email/contact-lists/#{@list_name}/contacts/test%2Bbar%40example.com"
214 | end
215 |
216 | test "#delete_contact" do
217 | email = "test+bar@example.com"
218 | operation = SES.delete_contact(@list_name, email)
219 |
220 | assert operation.http_method == :delete
221 | assert operation.path == "/v2/email/contact-lists/#{@list_name}/contacts/test%2Bbar%40example.com"
222 | end
223 | end
224 |
225 | describe "suppressed destinations" do
226 | test "#put_suppressed_destination" do
227 | email = "test@example.com"
228 | operation = SES.put_suppressed_destination(email, :BOUNCE)
229 |
230 | expected_data = %{
231 | EmailAddress: email,
232 | Reason: :BOUNCE
233 | }
234 |
235 | assert operation.http_method == :put
236 | assert operation.path == "/v2/email/suppression/addresses"
237 | assert operation.data == expected_data
238 | end
239 |
240 | test "#delete_suppressed_destination" do
241 | email = "test+bar@example.com"
242 | operation = SES.delete_suppressed_destination(email)
243 |
244 | assert operation.http_method == :delete
245 | assert operation.path == "/v2/email/suppression/addresses/test%2Bbar%40example.com"
246 | end
247 | end
248 |
249 | describe "v2 API send_email" do
250 | test "simple html", context do
251 | content = %{
252 | Simple: %{
253 | Body: %{
254 | Html: %{
255 | Data: "
test email via elixir ses"
256 | }
257 | },
258 | Subject: %{Data: "test email via elixir ses"}
259 | }
260 | }
261 |
262 | expected_data = %{
263 | Content: content,
264 | Destination: context[:destination],
265 | EmailTags: [context[:tag]],
266 | FromEmailAddress: context[:email],
267 | ListManagementOptions: context[:list_management]
268 | }
269 |
270 | operation =
271 | SES.send_email_v2(
272 | context[:destination],
273 | content,
274 | context[:email],
275 | tags: [context[:tag]],
276 | list_management: context[:list_management]
277 | )
278 |
279 | assert operation.http_method == :post
280 | assert operation.path == "/v2/email/outbound-emails"
281 | assert operation.data == expected_data
282 | end
283 |
284 | test "simple text", context do
285 | content = %{
286 | Simple: %{
287 | Body: %{
288 | Text: %{
289 | Data: "test email via elixir ses"
290 | }
291 | },
292 | Subject: %{Data: "test email via elixir ses"}
293 | }
294 | }
295 |
296 | expected_data = %{
297 | Content: content,
298 | Destination: context[:destination],
299 | EmailTags: [context[:tag]],
300 | FromEmailAddress: context[:email],
301 | ListManagementOptions: context[:list_management]
302 | }
303 |
304 | operation =
305 | SES.send_email_v2(
306 | context[:destination],
307 | content,
308 | context[:email],
309 | tags: [context[:tag]],
310 | list_management: context[:list_management]
311 | )
312 |
313 | assert operation.http_method == :post
314 | assert operation.path == "/v2/email/outbound-emails"
315 | assert operation.data == expected_data
316 | end
317 | end
318 |
319 | describe "#send_email" do
320 | test "with required params only" do
321 | dst = %{to: ["success@simulator.amazonses.com"]}
322 | msg = %{body: %{}, subject: %{data: "subject"}}
323 |
324 | expected = %{
325 | "Action" => "SendEmail",
326 | "Destination.ToAddresses.member.1" => "success@simulator.amazonses.com",
327 | "Message.Subject.Data" => "subject",
328 | "Source" => "user@example.com"
329 | }
330 |
331 | assert expected == SES.send_email(dst, msg, "user@example.com").params
332 | end
333 |
334 | test "with all optional params" do
335 | dst = %{
336 | bcc: ["success@simulator.amazonses.com"],
337 | cc: ["success@simulator.amazonses.com"],
338 | to: ["success@simulator.amazonses.com", "bounce@simulator.amazonses.com"]
339 | }
340 |
341 | msg = SES.build_message("html", "text", "subject")
342 |
343 | expected = %{
344 | "Action" => "SendEmail",
345 | "ConfigurationSetName" => "test",
346 | "Destination.ToAddresses.member.1" => "success@simulator.amazonses.com",
347 | "Destination.ToAddresses.member.2" => "bounce@simulator.amazonses.com",
348 | "Destination.CcAddresses.member.1" => "success@simulator.amazonses.com",
349 | "Destination.BccAddresses.member.1" => "success@simulator.amazonses.com",
350 | "Message.Body.Html.Data" => "html",
351 | "Message.Body.Html.Charset" => "UTF-8",
352 | "Message.Body.Text.Data" => "text",
353 | "Message.Body.Text.Charset" => "UTF-8",
354 | "Message.Subject.Data" => "subject",
355 | "Message.Subject.Charset" => "UTF-8",
356 | "ReplyToAddresses.member.1" => "user@example.com",
357 | "ReplyToAddresses.member.2" => "user1@example.com",
358 | "ReturnPath" => "feedback@example.com",
359 | "ReturnPathArn" => "arn:aws:ses:us-east-1:123456789012:identity/example.com",
360 | "Source" => "user@example.com",
361 | "SourceArn" => "east-1:123456789012:identity/example.com",
362 | "Tags.member.1.Name" => "tag1",
363 | "Tags.member.1.Value" => "tag1value1",
364 | "Tags.member.2.Name" => "tag2",
365 | "Tags.member.2.Value" => "tag2value1"
366 | }
367 |
368 | assert expected ==
369 | SES.send_email(
370 | dst,
371 | msg,
372 | "user@example.com",
373 | configuration_set_name: "test",
374 | return_path: "feedback@example.com",
375 | return_path_arn: "arn:aws:ses:us-east-1:123456789012:identity/example.com",
376 | source_arn: "east-1:123456789012:identity/example.com",
377 | reply_to: ["user@example.com", "user1@example.com"],
378 | tags: [%{name: "tag1", value: "tag1value1"}, %{name: "tag2", value: "tag2value1"}]
379 | ).params
380 | end
381 | end
382 |
383 | describe "#send_raw_email" do
384 | setup do
385 | %{
386 | raw_email:
387 | "To: alice@example.com\r\nSubject: =?utf-8?Q?Welcome to the app.?=\r\nReply-To: chuck@example.com\r\nMime-Version: 1.0\r\nFrom: bob@example.com\r\nContent-Type: multipart/alternative; boundary=\"9081958709C029F90BFFF130\"\r\nCc: john@example.com\r\nBcc: jane@example.com\r\n\r\n--9081958709C029F90BFFF130\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nThanks for joining!\r\n\r\n--9081958709C029F90BFFF130\r\nContent-Type: text/html\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nThanks for joining!\r\n--9081958709C029F90BFFF130--",
388 | raw_email_data:
389 | "VG86IGFsaWNlQGV4YW1wbGUuY29tDQpTdWJqZWN0OiA9P3V0Zi04P1E/V2VsY29tZSB0byB0aGUgYXBwLj89DQpSZXBseS1UbzogY2h1Y2tAZXhhbXBsZS5jb20NCk1pbWUtVmVyc2lvbjogMS4wDQpGcm9tOiBib2JAZXhhbXBsZS5jb20NCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOyBib3VuZGFyeT0iOTA4MTk1ODcwOUMwMjlGOTBCRkZGMTMwIg0KQ2M6IGpvaG5AZXhhbXBsZS5jb20NCkJjYzogamFuZUBleGFtcGxlLmNvbQ0KDQotLTkwODE5NTg3MDlDMDI5RjkwQkZGRjEzMA0KQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBxdW90ZWQtcHJpbnRhYmxlDQoNClRoYW5rcyBmb3Igam9pbmluZyENCg0KLS05MDgxOTU4NzA5QzAyOUY5MEJGRkYxMzANCkNvbnRlbnQtVHlwZTogdGV4dC9odG1sDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBxdW90ZWQtcHJpbnRhYmxlDQoNCjxzdHJvbmc+VGhhbmtzIGZvciBqb2luaW5nITwvc3Ryb25nPg0KLS05MDgxOTU4NzA5QzAyOUY5MEJGRkYxMzAtLQ=="
390 | }
391 | end
392 |
393 | test "with required params only", %{raw_email: msg, raw_email_data: data} do
394 | expected = %{
395 | "Action" => "SendRawEmail",
396 | "RawMessage.Data" => data
397 | }
398 |
399 | assert expected == SES.send_raw_email(msg).params
400 | end
401 |
402 | test "with all optional params", %{raw_email: msg, raw_email_data: data} do
403 | expected = %{
404 | "Action" => "SendRawEmail",
405 | "ConfigurationSetName" => "test",
406 | "FromArn" => "east-1:123456789012:identity/example.com",
407 | "ReturnPathArn" => "arn:aws:ses:us-east-1:123456789012:identity/example.com",
408 | "Source" => "bob@example.com",
409 | "SourceArn" => "east-1:123456789012:identity/example.com",
410 | "Tags.member.1.Name" => "tag1",
411 | "Tags.member.1.Value" => "tag1value1",
412 | "Tags.member.2.Name" => "tag2",
413 | "Tags.member.2.Value" => "tag2value1",
414 | "RawMessage.Data" => data
415 | }
416 |
417 | assert expected ==
418 | SES.send_raw_email(msg,
419 | configuration_set_name: "test",
420 | from_arn: "east-1:123456789012:identity/example.com",
421 | return_path_arn: "arn:aws:ses:us-east-1:123456789012:identity/example.com",
422 | source: "bob@example.com",
423 | source_arn: "east-1:123456789012:identity/example.com",
424 | tags: [%{name: "tag1", value: "tag1value1"}, %{name: "tag2", value: "tag2value1"}]
425 | ).params
426 | end
427 | end
428 |
429 | describe "#send_templated_email" do
430 | test "with required params only" do
431 | dst = %{to: ["success@simulator.amazonses.com"]}
432 | src = "user@example.com"
433 | template_data = %{data1: "data1", data2: "data2"}
434 |
435 | expected = %{
436 | "Action" => "SendTemplatedEmail",
437 | "Destination.ToAddresses.member.1" => "success@simulator.amazonses.com",
438 | "Template" => "my_template",
439 | "Source" => "user@example.com",
440 | "TemplateData" => Jason.encode!(template_data)
441 | }
442 |
443 | assert expected == SES.send_templated_email(dst, src, "my_template", template_data).params
444 | end
445 |
446 | test "with all optional params" do
447 | dst = %{
448 | bcc: ["success@simulator.amazonses.com"],
449 | cc: ["success@simulator.amazonses.com"],
450 | to: ["success@simulator.amazonses.com", "bounce@simulator.amazonses.com"]
451 | }
452 |
453 | src = "user@example.com"
454 | template_data = %{data1: "data1", data2: "data2"}
455 |
456 | expected = %{
457 | "Action" => "SendTemplatedEmail",
458 | "ConfigurationSetName" => "test",
459 | "Destination.ToAddresses.member.1" => "success@simulator.amazonses.com",
460 | "Destination.ToAddresses.member.2" => "bounce@simulator.amazonses.com",
461 | "Destination.CcAddresses.member.1" => "success@simulator.amazonses.com",
462 | "Destination.BccAddresses.member.1" => "success@simulator.amazonses.com",
463 | "ReplyToAddresses.member.1" => "user@example.com",
464 | "ReplyToAddresses.member.2" => "user1@example.com",
465 | "ReturnPath" => "feedback@example.com",
466 | "ReturnPathArn" => "arn:aws:ses:us-east-1:123456789012:identity/example.com",
467 | "Source" => "user@example.com",
468 | "SourceArn" => "east-1:123456789012:identity/example.com",
469 | "Tags.member.1.Name" => "tag1",
470 | "Tags.member.1.Value" => "tag1value1",
471 | "Tags.member.2.Name" => "tag2",
472 | "Tags.member.2.Value" => "tag2value1",
473 | "Template" => "my_template",
474 | "TemplateData" => Jason.encode!(template_data)
475 | }
476 |
477 | assert expected ==
478 | SES.send_templated_email(
479 | dst,
480 | src,
481 | "my_template",
482 | template_data,
483 | configuration_set_name: "test",
484 | return_path: "feedback@example.com",
485 | return_path_arn: "arn:aws:ses:us-east-1:123456789012:identity/example.com",
486 | source_arn: "east-1:123456789012:identity/example.com",
487 | reply_to: ["user@example.com", "user1@example.com"],
488 | tags: [%{name: "tag1", value: "tag1value1"}, %{name: "tag2", value: "tag2value1"}]
489 | ).params
490 | end
491 | end
492 |
493 | describe "#send_bulk_templated_email" do
494 | test "with required params only" do
495 | template = "my_template"
496 | source = "user@example.com"
497 |
498 | replacement_template_data1 = %{data1: "value1"}
499 | replacement_template_data2 = %{data1: "value2"}
500 |
501 | destinations = [
502 | %{
503 | destination: %{to: ["email1@email.com", "email2@email.com"]},
504 | replacement_template_data: replacement_template_data1
505 | },
506 | %{
507 | destination: %{
508 | to: ["email3@email.com"],
509 | cc: ["email4@email.com", "email5@email.com"],
510 | bcc: ["email6@email.com", "email7@email.com"]
511 | },
512 | replacement_template_data: replacement_template_data2
513 | },
514 | %{destination: %{to: ["email8@email.com"]}}
515 | ]
516 |
517 | expected = %{
518 | "Action" => "SendBulkTemplatedEmail",
519 | "Template" => "my_template",
520 | "Source" => "user@example.com",
521 | "Destinations.member.1.Destination.ToAddresses.member.1" => "email1@email.com",
522 | "Destinations.member.1.Destination.ToAddresses.member.2" => "email2@email.com",
523 | "Destinations.member.1.ReplacementTemplateData" => Jason.encode!(replacement_template_data1),
524 | "Destinations.member.2.Destination.ToAddresses.member.1" => "email3@email.com",
525 | "Destinations.member.2.Destination.CcAddresses.member.1" => "email4@email.com",
526 | "Destinations.member.2.Destination.CcAddresses.member.2" => "email5@email.com",
527 | "Destinations.member.2.Destination.BccAddresses.member.1" => "email6@email.com",
528 | "Destinations.member.2.Destination.BccAddresses.member.2" => "email7@email.com",
529 | "Destinations.member.2.ReplacementTemplateData" => Jason.encode!(replacement_template_data2),
530 | "Destinations.member.3.Destination.ToAddresses.member.1" => "email8@email.com",
531 | "DefaultTemplateData" => "{}"
532 | }
533 |
534 | assert expected == SES.send_bulk_templated_email(template, source, destinations).params
535 | end
536 |
537 | test "with all optional params" do
538 | template = "my_template"
539 | source = "user@example.com"
540 |
541 | replacement_template_data1 = %{data1: "value1"}
542 | replacement_template_data2 = %{data1: "value2"}
543 | default_template_data = %{data1: "DefaultValue"}
544 |
545 | destinations = [
546 | %{
547 | destination: %{to: ["email1@email.com", "email2@email.com"]},
548 | replacement_template_data: replacement_template_data1
549 | },
550 | %{
551 | destination: %{
552 | to: ["email3@email.com"],
553 | cc: ["email4@email.com", "email5@email.com"],
554 | bcc: ["email6@email.com", "email7@email.com"]
555 | },
556 | replacement_template_data: replacement_template_data2
557 | },
558 | %{destination: %{to: ["email8@email.com"]}}
559 | ]
560 |
561 | expected = %{
562 | "Action" => "SendBulkTemplatedEmail",
563 | "ConfigurationSetName" => "test",
564 | "Template" => "my_template",
565 | "Source" => "user@example.com",
566 | "Destinations.member.1.Destination.ToAddresses.member.1" => "email1@email.com",
567 | "Destinations.member.1.Destination.ToAddresses.member.2" => "email2@email.com",
568 | "Destinations.member.1.ReplacementTemplateData" => Jason.encode!(replacement_template_data1),
569 | "Destinations.member.2.Destination.ToAddresses.member.1" => "email3@email.com",
570 | "Destinations.member.2.Destination.CcAddresses.member.1" => "email4@email.com",
571 | "Destinations.member.2.Destination.CcAddresses.member.2" => "email5@email.com",
572 | "Destinations.member.2.Destination.BccAddresses.member.1" => "email6@email.com",
573 | "Destinations.member.2.Destination.BccAddresses.member.2" => "email7@email.com",
574 | "Destinations.member.2.ReplacementTemplateData" => Jason.encode!(replacement_template_data2),
575 | "Destinations.member.3.Destination.ToAddresses.member.1" => "email8@email.com",
576 | "DefaultTemplateData" => Jason.encode!(default_template_data),
577 | "ReplyToAddresses.member.1" => "user@example.com",
578 | "ReplyToAddresses.member.2" => "user1@example.com",
579 | "ReturnPath" => "feedback@example.com",
580 | "ReturnPathArn" => "arn:aws:ses:us-east-1:123456789012:identity/example.com",
581 | "SourceArn" => "east-1:123456789012:identity/example.com",
582 | "Tags.member.1.Name" => "tag1",
583 | "Tags.member.1.Value" => "tag1value1",
584 | "Tags.member.2.Name" => "tag2",
585 | "Tags.member.2.Value" => "tag2value1"
586 | }
587 |
588 | assert expected ==
589 | SES.send_bulk_templated_email(
590 | template,
591 | source,
592 | destinations,
593 | default_template_data: default_template_data,
594 | configuration_set_name: "test",
595 | return_path: "feedback@example.com",
596 | return_path_arn: "arn:aws:ses:us-east-1:123456789012:identity/example.com",
597 | source_arn: "east-1:123456789012:identity/example.com",
598 | reply_to: ["user@example.com", "user1@example.com"],
599 | tags: [%{name: "tag1", value: "tag1value1"}, %{name: "tag2", value: "tag2value1"}]
600 | ).params
601 | end
602 | end
603 |
604 | test "#delete_identity", ctx do
605 | expected = %{"Action" => "DeleteIdentity", "Identity" => ctx.email}
606 | assert expected == SES.delete_identity(ctx.email).params
607 | end
608 |
609 | describe "#set_identity_notification_topic" do
610 | test "accepts correct notification types", ctx do
611 | Enum.each([:bounce, :complaint, :delivery], fn type ->
612 | notification_type = Atom.to_string(type) |> String.capitalize()
613 |
614 | expected = %{
615 | "Action" => "SetIdentityNotificationTopic",
616 | "Identity" => ctx.email,
617 | "NotificationType" => notification_type
618 | }
619 |
620 | assert expected == SES.set_identity_notification_topic(ctx.email, type).params
621 | end)
622 | end
623 |
624 | test "optional params", ctx do
625 | sns_topic_arn = "arn:aws:sns:us-east-1:123456789012:my_corporate_topic:02034b43-fefa-4e07-a5eb-3be56f8c54ce"
626 |
627 | expected = %{
628 | "Action" => "SetIdentityNotificationTopic",
629 | "Identity" => ctx.email,
630 | "NotificationType" => "Bounce",
631 | "SnsTopic" => sns_topic_arn
632 | }
633 |
634 | assert expected == SES.set_identity_notification_topic(ctx.email, :bounce, sns_topic: sns_topic_arn).params
635 | end
636 | end
637 |
638 | test "#set_identity_feedback_forwarding_enabled", ctx do
639 | enabled = true
640 |
641 | expected = %{
642 | "Action" => "SetIdentityFeedbackForwardingEnabled",
643 | "ForwardingEnabled" => enabled,
644 | "Identity" => ctx.email
645 | }
646 |
647 | assert expected == SES.set_identity_feedback_forwarding_enabled(enabled, ctx.email).params
648 | end
649 |
650 | test "#set_identity_headers_in_notifications_enabled", ctx do
651 | enabled = true
652 |
653 | expected = %{
654 | "Action" => "SetIdentityHeadersInNotificationsEnabled",
655 | "Identity" => ctx.email,
656 | "Enabled" => enabled,
657 | "NotificationType" => "Delivery"
658 | }
659 |
660 | assert expected == SES.set_identity_headers_in_notifications_enabled(ctx.email, :delivery, enabled).params
661 | end
662 |
663 | test "#get_template" do
664 | templateName = "MyTemplate"
665 |
666 | expected = %{
667 | "Action" => "GetTemplate",
668 | "TemplateName" => templateName
669 | }
670 |
671 | assert expected == SES.get_template(templateName).params
672 | end
673 |
674 | test "#list_templates" do
675 | expected = %{
676 | "Action" => "ListTemplates",
677 | "MaxItems" => 1,
678 | "NextToken" => "QUFBQUF"
679 | }
680 |
681 | assert expected == SES.list_templates(max_items: 1, next_token: "QUFBQUF").params
682 | end
683 |
684 | describe "#create_template" do
685 | test "with required params only" do
686 | templateName = "MyTemplate"
687 | subject = "Greetings, {{name}}!"
688 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
689 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
690 |
691 | expected = %{
692 | "Action" => "CreateTemplate",
693 | "Template.TemplateName" => templateName,
694 | "Template.SubjectPart" => subject,
695 | "Template.HtmlPart" => html,
696 | "Template.TextPart" => text
697 | }
698 |
699 | assert expected == SES.create_template(templateName, subject, html, text).params
700 | end
701 |
702 | test "without text part" do
703 | templateName = "MyTemplate"
704 | subject = "Greetings, {{name}}!"
705 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
706 |
707 | expected = %{
708 | "Action" => "CreateTemplate",
709 | "Template.TemplateName" => templateName,
710 | "Template.SubjectPart" => subject,
711 | "Template.HtmlPart" => html
712 | }
713 |
714 | assert expected == SES.create_template(templateName, subject, html, nil).params
715 | end
716 |
717 | test "with all optional params" do
718 | templateName = "MyTemplate"
719 | subject = "Greetings, {{name}}!"
720 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
721 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
722 |
723 | expected = %{
724 | "Action" => "CreateTemplate",
725 | "Template.TemplateName" => templateName,
726 | "Template.SubjectPart" => subject,
727 | "Template.HtmlPart" => html,
728 | "Template.TextPart" => text,
729 | "ConfigurationSetName" => "test"
730 | }
731 |
732 | assert expected == SES.create_template(templateName, subject, html, text, configuration_set_name: "test").params
733 | end
734 | end
735 |
736 | describe "#update_template" do
737 | test "with required params only" do
738 | templateName = "MyTemplate"
739 | subject = "Greetings, {{name}}!"
740 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
741 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
742 |
743 | expected = %{
744 | "Action" => "UpdateTemplate",
745 | "Template.TemplateName" => templateName,
746 | "Template.SubjectPart" => subject,
747 | "Template.HtmlPart" => html,
748 | "Template.TextPart" => text
749 | }
750 |
751 | assert expected == SES.update_template(templateName, subject, html, text).params
752 | end
753 |
754 | test "without text part" do
755 | templateName = "MyTemplate"
756 | subject = "Greetings, {{name}}!"
757 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
758 |
759 | expected = %{
760 | "Action" => "UpdateTemplate",
761 | "Template.TemplateName" => templateName,
762 | "Template.SubjectPart" => subject,
763 | "Template.HtmlPart" => html
764 | }
765 |
766 | assert expected == SES.update_template(templateName, subject, html, nil).params
767 | end
768 |
769 | test "with all optional params" do
770 | templateName = "MyTemplate"
771 | subject = "Greetings, {{name}}!"
772 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
773 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
774 |
775 | expected = %{
776 | "Action" => "UpdateTemplate",
777 | "Template.TemplateName" => templateName,
778 | "Template.SubjectPart" => subject,
779 | "Template.HtmlPart" => html,
780 | "Template.TextPart" => text,
781 | "ConfigurationSetName" => "test"
782 | }
783 |
784 | assert expected == SES.update_template(templateName, subject, html, text, configuration_set_name: "test").params
785 | end
786 | end
787 |
788 | test "#delete_template" do
789 | templateName = "MyTemplate"
790 |
791 | expected = %{
792 | "Action" => "DeleteTemplate",
793 | "TemplateName" => templateName
794 | }
795 |
796 | assert expected == SES.delete_template(templateName).params
797 | end
798 |
799 | describe "get_email_template/1" do
800 | test "with param" do
801 | templateName = "MyTemplate"
802 | expected = "/v2/email/templates/#{templateName}"
803 | assert expected == SES.get_email_template(templateName).path
804 | end
805 | end
806 |
807 | describe "list_email_templates/1" do
808 | test "with pagination params" do
809 | expected = "/v2/email/templates?NextToken=QUFBQUF&PageSize=1"
810 | assert expected == SES.list_email_templates(page_size: 1, next_token: "QUFBQUF").path
811 | end
812 |
813 | test "without pagination params" do
814 | expected = "/v2/email/templates?"
815 | assert expected == SES.list_email_templates().path
816 | end
817 | end
818 |
819 | describe "create_email_template/4" do
820 | test "with required params only" do
821 | templateName = "MyTemplate"
822 | subject = "Greetings, {{name}}!"
823 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
824 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
825 |
826 | expected = %{
827 | "TemplateName" => templateName,
828 | "TemplateContent" => %{
829 | "Subject" => subject,
830 | "Html" => html,
831 | "Text" => text
832 | }
833 | }
834 |
835 | assert expected == SES.create_email_template(templateName, subject, html, text).data
836 | end
837 |
838 | test "without text part" do
839 | templateName = "MyTemplate"
840 | subject = "Greetings, {{name}}!"
841 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
842 |
843 | expected = %{
844 | "TemplateName" => templateName,
845 | "TemplateContent" => %{
846 | "Subject" => subject,
847 | "Html" => html
848 | }
849 | }
850 |
851 | assert expected == SES.create_email_template(templateName, subject, html, nil).data
852 | end
853 | end
854 |
855 | describe "update_email_template/4" do
856 | test "with required params only" do
857 | templateName = "MyTemplate"
858 | subject = "Greetings, {{name}}!"
859 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
860 | text = "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
861 |
862 | expected = %{
863 | "TemplateContent" => %{
864 | "Subject" => subject,
865 | "Html" => html,
866 | "Text" => text
867 | }
868 | }
869 |
870 | assert expected == SES.update_email_template(templateName, subject, html, text).data
871 | end
872 |
873 | test "without text part" do
874 | templateName = "MyTemplate"
875 | subject = "Greetings, {{name}}!"
876 | html = "Hello {{name}},
Your favorite animal is {{favoriteanimal}}.
"
877 |
878 | expected = %{
879 | "TemplateContent" => %{
880 | "Subject" => subject,
881 | "Html" => html
882 | }
883 | }
884 |
885 | assert expected == SES.update_email_template(templateName, subject, html, nil).data
886 | end
887 | end
888 |
889 | describe "delete_email_template/1" do
890 | test "with required param" do
891 | templateName = "MyTemplate"
892 | expected = "/v2/email/templates/MyTemplate"
893 | assert expected == SES.delete_email_template(templateName).path
894 | end
895 | end
896 |
897 | describe "test_render_email_template/2" do
898 | test "with params" do
899 | template_name = "MyTemplate"
900 | template_data = %{data1: "data1", data2: "data2"}
901 | expected = %{"TemplateData" => ~s({"data1":"data1","data2":"data2"})}
902 | assert expected == SES.test_render_email_template(template_name, template_data).data
903 | end
904 | end
905 |
906 | test "#create custom email verification template" do
907 | template_name = "MyTemplate"
908 | from_email_address = "test@example.com"
909 | template_subject = "Verified with ExAWS!"
910 | template_content = "This is some custom content"
911 | success_redirection_url = "https://example.com/success"
912 | failure_redirection_url = "https://example.com/failure"
913 |
914 | expected = %{
915 | "Action" => "CreateCustomVerificationEmailTemplate",
916 | "TemplateName" => template_name,
917 | "FromEmailAddress" => from_email_address,
918 | "TemplateSubject" => template_subject,
919 | "TemplateContent" => template_content,
920 | "SuccessRedirectionURL" => success_redirection_url,
921 | "FailureRedirectionURL" => failure_redirection_url
922 | }
923 |
924 | assert expected ==
925 | SES.create_custom_verification_email_template(
926 | template_name,
927 | from_email_address,
928 | template_subject,
929 | template_content,
930 | success_redirection_url,
931 | failure_redirection_url
932 | ).params
933 | end
934 |
935 | test "#update custom email verification template" do
936 | template_name = "MyTemplate"
937 | from_email_address = "test@example.com"
938 | template_subject = "Verified with ExAWS!"
939 | template_content = "This is some custom content"
940 | success_redirection_url = "https://example.com/success"
941 | failure_redirection_url = "https://example.com/failure"
942 |
943 | expected = %{
944 | "Action" => "UpdateCustomVerificationEmailTemplate",
945 | "TemplateName" => template_name,
946 | "FromEmailAddress" => from_email_address,
947 | "TemplateSubject" => template_subject,
948 | "TemplateContent" => template_content,
949 | "SuccessRedirectionURL" => success_redirection_url,
950 | "FailureRedirectionURL" => failure_redirection_url
951 | }
952 |
953 | assert expected ==
954 | SES.update_custom_verification_email_template(
955 | template_name: template_name,
956 | from_email_address: from_email_address,
957 | template_subject: template_subject,
958 | template_content: template_content,
959 | success_redirection_url: success_redirection_url,
960 | failure_redirection_url: failure_redirection_url
961 | ).params
962 | end
963 |
964 | test "#delete custom email verification template" do
965 | template_name = "MyTemplate"
966 |
967 | expected = %{
968 | "Action" => "DeleteCustomVerificationEmailTemplate",
969 | "TemplateName" => template_name
970 | }
971 |
972 | assert expected == SES.delete_custom_verification_email_template(template_name).params
973 | end
974 |
975 | test "#list custom verification email templates" do
976 | max_results = 25
977 | next_token = "token"
978 |
979 | expected = %{
980 | "Action" => "ListCustomVerificationEmailTemplates",
981 | "MaxResults" => max_results,
982 | "NextToken" => next_token
983 | }
984 |
985 | assert expected ==
986 | SES.list_custom_verification_email_templates(max_results: max_results, next_token: next_token).params
987 | end
988 |
989 | test "#send verification email with custom template" do
990 | template_name = "MyTemplate"
991 | email_address = "test@example.com"
992 | configuration_set_name = "MyConfigurationSet"
993 |
994 | expected = %{
995 | "Action" => "SendCustomVerificationEmail",
996 | "TemplateName" => template_name,
997 | "EmailAddress" => email_address,
998 | "ConfigurationSetName" => configuration_set_name
999 | }
1000 |
1001 | assert expected ==
1002 | SES.send_custom_verification_email(email_address, template_name,
1003 | configuration_set_name: configuration_set_name
1004 | ).params
1005 | end
1006 |
1007 | describe "list_custom_verification_email_templates_v2/1" do
1008 | test "with options" do
1009 | expected = "/v2/email/custom-verification-email-templates?NextToken=QUFBQUF&PageSize=1"
1010 | assert expected == SES.list_custom_verification_email_templates_v2(%{page_size: 1, next_token: "QUFBQUF"}).path
1011 | end
1012 |
1013 | test "without options" do
1014 | expected = "/v2/email/custom-verification-email-templates?"
1015 | assert expected == SES.list_custom_verification_email_templates_v2().path
1016 | end
1017 | end
1018 |
1019 | describe "get_custom_verification_email_templates_v2/1" do
1020 | test "with param" do
1021 | template_name = "MyTemplate"
1022 | expected = "/v2/email/custom-verification-email-templates/#{template_name}"
1023 | assert expected == SES.get_custom_verification_email_template_v2(template_name).path
1024 | end
1025 | end
1026 |
1027 | describe "create_custom_verification_email_template_v2/6" do
1028 | test "with params" do
1029 | template_name = "MyTemplate"
1030 | from_email_address = "test@example.com"
1031 | template_subject = "Verified with ExAWS!"
1032 | template_content = "This is some custom content"
1033 | success_redirection_url = "https://example.com/success"
1034 | failure_redirection_url = "https://example.com/failure"
1035 |
1036 | expected = %{
1037 | "TemplateName" => template_name,
1038 | "FromEmailAddress" => from_email_address,
1039 | "TemplateSubject" => template_subject,
1040 | "TemplateContent" => template_content,
1041 | "SuccessRedirectionURL" => success_redirection_url,
1042 | "FailureRedirectionURL" => failure_redirection_url
1043 | }
1044 |
1045 | assert expected ==
1046 | SES.create_custom_verification_email_template_v2(
1047 | template_name,
1048 | from_email_address,
1049 | template_subject,
1050 | template_content,
1051 | success_redirection_url,
1052 | failure_redirection_url
1053 | ).data
1054 | end
1055 | end
1056 |
1057 | describe "update_custom_verification_email_template_v2/2" do
1058 | test "with all options" do
1059 | template_name = "MyTemplate"
1060 | from_email_address = "test@example.com"
1061 | template_subject = "Verified with ExAWS!"
1062 | template_content = "This is some custom content"
1063 | success_redirection_url = "https://example.com/success"
1064 | failure_redirection_url = "https://example.com/failure"
1065 |
1066 | expected = %{
1067 | "FromEmailAddress" => from_email_address,
1068 | "TemplateSubject" => template_subject,
1069 | "TemplateContent" => template_content,
1070 | "SuccessRedirectionURL" => success_redirection_url,
1071 | "FailureRedirectionURL" => failure_redirection_url
1072 | }
1073 |
1074 | assert expected ==
1075 | SES.update_custom_verification_email_template_v2(template_name,
1076 | from_email_address: from_email_address,
1077 | template_subject: template_subject,
1078 | template_content: template_content,
1079 | success_redirection_url: success_redirection_url,
1080 | failure_redirection_url: failure_redirection_url
1081 | ).data
1082 | end
1083 |
1084 | test "without options" do
1085 | template_name = "MyTemplate"
1086 | assert %{} == SES.update_custom_verification_email_template_v2(template_name).data
1087 | end
1088 | end
1089 |
1090 | describe "delete_custom_verification_email_template_v2/1" do
1091 | test "with param" do
1092 | template_name = "MyTemplate"
1093 | expected = "/v2/email/custom-verification-email-templates/#{template_name}"
1094 | assert expected == SES.delete_custom_verification_email_template_v2(template_name).path
1095 | end
1096 | end
1097 |
1098 | describe "send_custom_verification_email_v2/3" do
1099 | test "with required params" do
1100 | template_name = "MyTemplate"
1101 | email_address = "test@example.com"
1102 |
1103 | expected = %{
1104 | "TemplateName" => template_name,
1105 | "EmailAddress" => email_address
1106 | }
1107 |
1108 | assert expected ==
1109 | SES.send_custom_verification_email_v2(email_address, template_name).data
1110 | end
1111 |
1112 | test "with all options" do
1113 | template_name = "MyTemplate"
1114 | email_address = "test@example.com"
1115 | configuration_set_name = "MyConfigurationSet"
1116 |
1117 | expected = %{
1118 | "TemplateName" => template_name,
1119 | "EmailAddress" => email_address,
1120 | "ConfigurationSetName" => configuration_set_name
1121 | }
1122 |
1123 | assert expected ==
1124 | SES.send_custom_verification_email_v2(email_address, template_name,
1125 | configuration_set_name: configuration_set_name
1126 | ).data
1127 | end
1128 | end
1129 | end
1130 |
--------------------------------------------------------------------------------