├── .formatter.exs
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── README.md
├── config
└── config.exs
├── lib
└── ex_aws
│ ├── ses.ex
│ └── ses
│ └── parsers.ex
├── mix.exs
├── mix.lock
└── test
├── lib
├── ses
│ └── parser_test.exs
└── ses_test.exs
└── test_helper.exs
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | MalformedInput
416 |
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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 = "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 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(exclude: [:integration]) 2 | --------------------------------------------------------------------------------