diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index bbbe167..fefa866 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -179,6 +179,13 @@ export default defineConfig({ { text: 'Wireguard', link: '/services/vpn/wireguard' }, ] }, + { + text: 'Transactional Mail', + collapsed: true, + items: [ + { text: 'Amazon SES', link: '/services/transactional-mail/amazon-ses' }, + ] + }, ] }, { diff --git a/docs/services/transactional-mail/amazon-ses.md b/docs/services/transactional-mail/amazon-ses.md new file mode 100644 index 0000000..462eee7 --- /dev/null +++ b/docs/services/transactional-mail/amazon-ses.md @@ -0,0 +1,189 @@ +--- +outline: deep +--- + +# Amazon SES — Sending from Python and JavaScript + +## Project Configuration + +- SES Region: `eu-north-1` (Europe, Stockholm) +- Verified domain: `fastcomputing.net` +- Example sender: `noreply@fastcomputing.net` +- Recommended API: Amazon SES v2 + +## Authentication + +Do not put AWS credentials in the code or in the repository. + +### Local Development + +Log in with the AWS CLI: + +```sh +aws login +``` + +This opens a browser to authenticate and caches temporary credentials locally. Boto3 reads them automatically through the AWS CLI credential chain, so there is no need to export `AWS_ACCESS_KEY_ID` or `AWS_SECRET_ACCESS_KEY`. + +Set the region for the session: + +```sh +export AWS_DEFAULT_REGION="eu-north-1" +``` + +On Windows PowerShell: + +```powershell +$env:AWS_DEFAULT_REGION="eu-north-1" +``` + +### EC2 + +Attach an IAM role authorized to send with SES to the instance. Do not copy the local access keys onto the server: the SDKs automatically retrieve temporary credentials from the EC2 role. + +Minimal IAM permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ses:SendEmail", + "ses:SendRawEmail" + ], + "Resource": "arn:aws:ses:eu-north-1:ACCOUNT_ID:identity/fastcomputing.net" + } + ] +} +``` + +Replace `ACCOUNT_ID` with the numeric ID of the AWS account. + +If a configuration set is specified or applied during sending, also add its ARN to `Resource`, for example: + +``` +arn:aws:ses:eu-north-1:ACCOUNT_ID:configuration-set/CONFIGURATION_SET_NAME +``` + +## Python + +Installation: + +```sh +pip install boto3 +``` + +Sending: + +```python +import boto3 + +ses = boto3.client("sesv2", region_name="eu-north-1") + +response = ses.send_email( + FromEmailAddress="Fast Computing ", + Destination={ + "ToAddresses": ["recipient@example.com"], + }, + ReplyToAddresses=["nicola.demo@fastcomputing.net"], + Content={ + "Simple": { + "Subject": { + "Data": "Test email", + "Charset": "UTF-8", + }, + "Body": { + "Text": { + "Data": "This email was sent via Amazon SES.", + "Charset": "UTF-8", + }, + "Html": { + "Data": ( + "

Test email

" + "

This email was sent via Amazon SES.

" + ), + "Charset": "UTF-8", + }, + }, + } + }, +) + +print("Email sent:", response["MessageId"]) +``` + +If Botocore reports that the login provider dependency is missing: + +```sh +pip install "botocore[crt]" +``` + +## JavaScript / Node.js + +Installation: + +```sh +npm install @aws-sdk/client-sesv2 +``` + +Sending: + +```js +import { + SESv2Client, + SendEmailCommand, +} from "@aws-sdk/client-sesv2"; + +const ses = new SESv2Client({ + region: "eu-north-1", +}); + +const response = await ses.send(new SendEmailCommand({ + FromEmailAddress: "Fast Computing ", + Destination: { + ToAddresses: ["recipient@example.com"], + }, + ReplyToAddresses: ["nicola.demo@fastcomputing.net"], + Content: { + Simple: { + Subject: { + Data: "Test email", + Charset: "UTF-8", + }, + Body: { + Text: { + Data: "This email was sent via Amazon SES.", + Charset: "UTF-8", + }, + Html: { + Data: ` +

Test email

+

This email was sent via Amazon SES.

+ `, + Charset: "UTF-8", + }, + }, + }, + }, +})); + +console.log("Email sent:", response.MessageId); +``` + +To use `import`, add the following to `package.json`: + +```json +{ + "type": "module" +} +``` + +## Notes + +- The sender must belong to the verified domain. +- The region in the code and in the IAM policy must be `eu-north-1`. +- Do not pass `ConfigurationSetName` unless it is necessary. +- Store and monitor the `MessageId` returned by SES. +- Handle bounces and complaints separately before increasing volumes.