Kaan Barmore-Genç
82a9aa3779
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
135 lines
5.9 KiB
Markdown
135 lines
5.9 KiB
Markdown
---
|
|
title: "Amazon SES Production Access Approval"
|
|
date: 2023-10-03T04:34:37Z
|
|
toc: false
|
|
images:
|
|
tags:
|
|
---
|
|
|
|
I've been setting up something for a relative who's trying to start a business,
|
|
and as part of that they needed to be able to send transactional emails. After
|
|
looking through some of the services available, Amazon SES looked like a good
|
|
option. You get a fairly large number of emails for free every day, and
|
|
additional emails are available at a decent price paid per email rather than the
|
|
big "pay $X to get 10,000 more emails" kind of packages that most other
|
|
providers seem to offer.
|
|
|
|
## Let's sign up with AWS!
|
|
|
|
So I signed up for AWS, created my access tokens, imported the SES client into
|
|
the app, and verified a domain so I could start testing. Phew! It did work
|
|
pretty painlessly at this point, I was sending my test emails and receiving them
|
|
very quickly.
|
|
|
|
But there's a weird thing Amazon does for SES where your SES account will start
|
|
out in a sandbox mode where you can only send test emails to your own verified
|
|
domains or inboxes. To actually start sending emails, you need to request
|
|
approval first. I thought this was sort of a formality, but it turns out that's
|
|
not the case because AWS very quickly rejected my request. Now I get it, spam
|
|
emails are a big problem and Amazon doesn't want spammers abusing SES, which
|
|
could hurt SES's email reputation. If you are unfamiliar, if email services
|
|
decide your IP address is a spammer, your emails will start going straight into
|
|
the spam filter. With SES, you use their shared IP addresses unless you pay
|
|
extra to reserve your own IP address, so a spammer using SES would cause issues
|
|
for everyone who's not paying for a dedicated IP.
|
|
|
|
This was very frustrating though, because I then decided to sign up for SendGrid
|
|
who banned my account immediately before I could even complete the sign up. It
|
|
was a bizarre experience to receive a "here's the code to verify your email
|
|
address" email and a "you have been banned, goodbye" email simultaneously. What
|
|
did I even do?
|
|
|
|
I was able to get approved after some back and forth with the AWS support team,
|
|
and I wanted to write about this in case others hit this same issue because the
|
|
feedback AWS gives you is basically nonexistant. When my request was rejected,
|
|
the response I got just said:
|
|
|
|
> We reviewed your request and determined that your use of Amazon SES could have
|
|
> a negative impact on our service. We are denying this request to prevent other
|
|
> Amazon SES customers from experiencing interruptions in service. For security
|
|
> purposes, we are unable to provide specific details.
|
|
|
|
Oof. I was especially confused because I had explicitly described that I would
|
|
only be sending transactional emails to paying customers, and only once just to
|
|
deliver their order after they had paid. This is as far away as you can get from
|
|
spam, the only way you would receive an email is if you asked for it and paid.
|
|
But I think I understand a few things AWS customer support team is looking for
|
|
before they approve your request. I wish they would just describe this, but I
|
|
guess that's the "security purposes".
|
|
|
|
## What to put in your production access request
|
|
|
|
1. Note how many emails you'll be sending. Give your best estimate. This won't
|
|
be your sending limit, I said I'd send 100 emails a day and got a quota for
|
|
50,000 emails per day. So there's no need to over-estimate to get a higher
|
|
limit or to lie that you'll send less, your best estimate should be good
|
|
enough.
|
|
2. Explain what you'll do with bounces, complaints, and unsubscribe requests.
|
|
These might not even make sense for you, for example in my case the emails
|
|
will only be sent to paid customers after their successful transaction, there
|
|
are no recurring emails so nothing to unsubscribe from. But explaining that
|
|
wasn't enough for AWS, I had to also explain that I would stop sending emails
|
|
to any bounced addresses or to anyone who complains. If you don't have the
|
|
capability to do that in your code, make sure to implement it first.
|
|
3. Attach at least 1 screenshot of the emails you'll be sending. A picture is
|
|
worth a thousand words and all, I think this gets your point across much more
|
|
quickly. I'm not sure if you can attach a picture to your initial request,
|
|
but I think you can comment again to attach a picture afterwards without
|
|
waiting for them to reject you.
|
|
4. Write down where you'll be getting the email addresses to send emails to,
|
|
even if it feels obvious. I had already said I was going to send emails to
|
|
customers who bought something, but I think this wasn't clear enough. In
|
|
followups I described that customers would enter their email addresses when
|
|
making a purchase, and added a screenshot of the checkout page where it
|
|
clearly says "your purchase will be sent to this email address". I think
|
|
showing this also helped.
|
|
|
|
I'm sure there are other things to consider and explain, but this worked for me.
|
|
If you get denied, reopen the case, add even more screenshots and information
|
|
and try again.
|
|
|
|
## Brevo
|
|
|
|
Alternatively, I also signed up for [Brevo](https://www.brevo.com/). It works,
|
|
and was honestly easier to set up than SES because I didn't have to pull in AWS
|
|
client libraries. Instead I just had to call `fetch` and that was it. Hey, here
|
|
is the code for that actually:
|
|
|
|
```ts
|
|
export type Email = {
|
|
sender: {
|
|
name: string;
|
|
email: string;
|
|
};
|
|
to: string;
|
|
content: {
|
|
text: string;
|
|
html: string;
|
|
};
|
|
subject: string;
|
|
};
|
|
|
|
export async function sendEmailBrevo({
|
|
sender,
|
|
to,
|
|
content: { html: htmlContent, text: textContent },
|
|
subject,
|
|
}: Email) {
|
|
await fetch("https://api.brevo.com/v3/smtp/email", {
|
|
method: "POST",
|
|
headers: {
|
|
accept: "application/json",
|
|
"content-type": "application/json",
|
|
"api-key": process.env.BREVO_API_KEY,
|
|
},
|
|
body: JSON.stringify({
|
|
sender,
|
|
to: [{ email: to }],
|
|
subject,
|
|
htmlContent,
|
|
textContent,
|
|
}),
|
|
});
|
|
}
|
|
```
|