Swoosh: Swoosh.Adapters.SMTP Vs. Swoosh.Adapters.AmazonSES

Hey Folks,

I just spent the last couple of days doing a deep dive into using Swoosh with Amazon’s simple email service, check out this troubleshooting post for a bit of background.

While doing so, I came to two different working solutions, one that sent emails via interacting with AmazonSES API directly, and the other used Swoosh’s SMTP adapter and sent emails via AmazonSES’s SMTP endpoint. Both seemed to have their own pros and cons. I thought this would be a good discussion to have and I’m genuinely interested in seeing what the more popular choice is.

Swoosh.Adapters.AmazonSES

To use Swoosh’s AmazonSES adapter the setup is somewhat simple. Your configuration is fairly straight forward and is as follows:

config :app_name, AppName.Mailer,
  adapter: Swoosh.Adapters.AmazonSES,
  region: "us-east-2", # yours may be different
  access_key: "ROOT_AWS_ACCESS_KEY",
  secret: "ROOT_AWS_SECRET_KEY"

This is where the simplicity ends. Because you’re not using the the standard SMTP adapter Swoosh.Adapters.SMTP you need to enable an API client and add a few dependencies.

To enable an API client first change this line in your config.exs from:

config :swoosh, :api_client, false

To

config :swoosh, :api_client, Swoosh.ApiClient.Hackney

# or

config :swoosh, :api_client, Swoosh.ApiClient.Finch

Swoosh works well with both Finch and Hackney out of the box. The next step is to add your dependencies to mix.exs, fetch and restart the phoenix server.

  defp deps do
    [
      {:gen_smtp, "~> 1.1.1"},
      {:hackney, "~> 1.18.0"} # or Finch
    ]
  end

You’ll also notice that you need to add gen_smtp, this is required for non SMTP adapters as they require a SMTP client.

The Swoosh.Adapters.AmazonSES has a very simple config but requires additional dependencies and if you noticed requires you to use and include your root admin AWS credentials. Because the Swoosh.Adapters.AmazonSE adapter interacts with the SES APIs directly, you need to use your root AWS access and secrete key and you cannot use a SMTP user/credentials with limited access. I.e. if your Phoenix secretes are compromised an attacker will have access to all of your AWS services not just AmazonSES.

Swoosh.Adapters.SMTP

The config.exs has a couple more fields and is as follows:

config :app_name, AppName.Mailer,
  adapter: Swoosh.Adapters.SMTP,
  relay: "email-smtp.us-east-2.amazonaws.com", # yours may be different
  username: "SMTP_AWS_ACCESS_KEY",
  password: "SMTP_AWS_SECRET_KEY",
  port: 25,
  retries: 2,
  no_mx_lookups: false

This should be all you need! Simply create an SMTP IAM user that has restricted access to the AmazonSES service and use those credentials as your username and password.

The Swoosh.Adapters.SMTP is easier to use out of box, has less additional dependencies and you can use SMTP credentials NOT your admin AWS credentials. However, this solution comes with all of the issues that SMTP has.

Conclusion

The Swoosh.Adapters.AmazonSES adapter has a fairly basic config.exs and I like that it directly interacts with the SES APIs. However, it creates a bit of a security issue as you have to have an admin level AWS credential in your Phoenix secrets. This doc outlines which credentials should be used in each situation and further illustrates this issue. The Swoosh.Adapters.AmazonSES also requires additional dependencies which might be confusing for those new to Phoenix or time consuming for those who just want something up and running as quickly as possible.

The Swoosh.Adapters.SMTP adapter has less dependencies and works right out of the box. It’s also not dependant on AmazonSES or a specific SMTP implementation. You can very quickly adapt it to work with another SMTP server. Also, when using it with AmazonSES you can use an IAM user/credentials that only has access to the AmazonSES service. However, you inevitably get all of the issues that come with SMTP. It’s an older protocol that’s showing it’s age and I’ve run into issues getting it to work with certain security protocols.

To be honest I’m a bit divided. I like interacting directly with the AmazonSES APIs but having to add admin credentials makes me a bit nervous. I like the simplicity of SMTP but, maybe I’m showing bias here, am not a fan of the protocol. It feels clunky, dated and restrictive in some situations. I’d love to hear everyone’s thoughts on this.

Also, if anyone has figured out how to use the Swoosh.Adapters.AmazonSES without admin AWS credentials, please post here! I was unable to figure it out and after reading the doc linked above came to the conclusion you couldn’t. But I’d love to be wrong on this for obvious reasons.

Thanks,
Scott

9 Likes

Swoosh allows for provider-specific options when sending - see the put_provider_option function in the Swoosh docs.

In particular, there’s a mention of using an IAM role along with the SES adapter:

2 Likes

@al2o3cr That’s awesome! I’m going to kick this around a bit, I’ll update the discussion.

1 Like

@al2o3cr So I’ve spent a few (very long) days getting this to work with an IAM user with restricted access. I’ve come across a fairly large discrepancy within the Swoosh docs.

As it turns out getting this to work with an IAM user is even easier than the docs imply. Sadly, after adding ex_aws and ex_aws_sts as dependencies, working out how to request a X-Amz-Security-Token and spending hours getting the IAM roles/policies correct, it turns out it was all unnecessary.

I was able to make this work by simply creating a user in IAM, and restricting the access to only sending SES emails. Here is the policy I used to do so:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
    ]
}

Two things to note for those trying to implement this:

  1. This IAM policy allows sending from ANY email address. You probably want to restrict this by filling out the resource field with one or more valid sender emails. Please see this AWS IAM doc for more information on restricting a user’s access to just sending emails.

  2. This should work with roles applied to an individual user or user group. I implemented this with an IAM policy but this should work with an IAM role as well.

Once I assigned this policy to the user and verified the receiver’s email address in SES, I could send emails regardless of if I added the |> put_provider_option(:security_token, "your_token_here") header or not. It just worked! All that was needed was the policy and the usage of the new user’s access_key and secret in place of the root credentials.

I found this to be fairly confusing as it’s clearly stated in the Swoosh docs. Can someone provide insight into why this might be? It looks like AWS recently revamped the SES dashboard and the service it’s self. Could this requirement have been removed when this was done?

To come back to the original topic, I prefer sending emails with the restricted IAM user far more than using SMTP or root credentials. It’s safer, less configuration and allows you to apply this policy or role to any new users or user groups you create in the future. I would recommend using this or a similar solution for anyone who finds themselves wanting to send emails via Amazon's SES and Swoosh.

Additional thoughts are welcome!

Thanks,
Scott

5 Likes

I don’t see how this follows. Access keys and secrets can be generated for any IAM user, not just the root user.

Hey @benwilson512 I updated the thread with a reply after the realization that this was possible. I wanted to update the original post but didn’t as it would make the first couple of replies nonsensical. Getting this to work is a bit confusing, I go into detail as how I setup my IAM user.

Super helpful. Thank you!

Just wanted to put this here in case it helps somebody else:

@ScriptyScott The line given in your original post wasn’t working for me on Phoenix 1.7.2:

config :swoosh, :api_client, Swoosh.ApiClient.Finch

It was causing the following error :

[error] GenServer #PID<0.931.0> terminating
** (ArgumentError) unknown registry: Swoosh.Finch
    (elixir 1.14.3) lib/registry.ex:1382: Registry.key_info!/1
    (elixir 1.14.3) lib/registry.ex:580: Registry.lookup/2

[the stack trace continues but I stopped here for the sake of brevity...]

I was able to copy the following line from config/prod.exs to get things working:

config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: TodoList.Finch
3 Likes