diff --git a/content/posts/2023.10.02.amazon-SES-production-access-approval.md b/content/posts/2023.10.02.amazon-SES-production-access-approval.md new file mode 100644 index 0000000..c1ea230 --- /dev/null +++ b/content/posts/2023.10.02.amazon-SES-production-access-approval.md @@ -0,0 +1,134 @@ +--- +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, + }), + }); +} +```