Invio email via SMTP su AWS con SES

In questo articolo illustro come si fa, senza dare di matto, a inviare email da un’istanza EC2 via SMTP con il servizio SES (Simple Email System) di Amazon AWS.

Nozioni generali

Prima di continuare con la lettura, è importante fare alcune premesse per chiarire alcuni punti fondamentali.

  1. Non si può inviare posta da una EC2 o in generale da AWS senza passare per il servizio SES;
  2. Per ogni regione AWS esiste uno e uno solo server SMTP;
  3. Non tutte le regioni hanno il servizio SMTP via SES abilitato;
  4. È possibile usare il server SMTP di una regione anche da istanze che non sono su quella regione (es Server SMTP su eu-west-1 e invio da EC2 a eu-south-1);
  5. Gli indirizzi di posta elettronica esterni che si intende usare vanno validati via SES;
  6. L’utente, le cui credenziali vengono usate per inviare per inviare posta, deve essere un utente IAM di AWS;
  7. La password da usare nell’SMTP relativa ad un certo utente, NON è la sua secret key, per ottenere la password da usare nell’SMTP (questa cosa è cambiata a partire dagli inizi del 2019).

Convertire la secret key di un utente IAM in password per SMTP

Per convertire la secret key di un utente in password da usare per l’invio di posta via SMTP è necessario utilizzare un algoritmo codificato in uno script.

ATTENZIONE
Lo script è stato prelevato da Obtaining your Amazon SES SMTP credentials, riferirsi a questo link per eventuali aggiornamenti.

Creare un file .py (anche sulla propria macchina) e assegnargli permessi di esecuzione:

touch aws-smtp-convert.py
chmod a+x aws-smtp-convert.py

Aprire un editor (vim, nano, gedit eccetera) e incollare il seguente codice (controlla prima sempre se esistono eventuali aggiornamenti al link https://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html?icmpid=docs_ses_console)

#!/usr/bin/env python3

import hmac
import hashlib
import base64
import argparse

SMTP_REGIONS = [
    'us-east-2',       # US East (Ohio)
    'us-east-1',       # US East (N. Virginia)
    'us-west-2',       # US West (Oregon)
    'ap-south-1',      # Asia Pacific (Mumbai)
    'ap-northeast-2',  # Asia Pacific (Seoul)
    'ap-southeast-1',  # Asia Pacific (Singapore)
    'ap-southeast-2',  # Asia Pacific (Sydney)
    'ap-northeast-1',  # Asia Pacific (Tokyo)
    'ca-central-1',    # Canada (Central)
    'eu-central-1',    # Europe (Frankfurt)
    'eu-west-1',       # Europe (Ireland)
    'eu-west-2',       # Europe (London)
    'sa-east-1',       # South America (Sao Paulo)
    'us-gov-west-1',   # AWS GovCloud (US)
]

# These values are required to calculate the signature. Do not change them.
DATE = "11111111" 
SERVICE = "ses" 
MESSAGE = "SendRawEmail" 
TERMINAL = "aws4_request" 
VERSION = 0x04

def sign(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()

def calculate_key(secret_access_key, region):
    if region not in SMTP_REGIONS:
        raise ValueError(f"The {region} Region doesn't have an SMTP endpoint.")

    signature = sign(("AWS4" + secret_access_key).encode('utf-8'), DATE)
    signature = sign(signature, region)
    signature = sign(signature, SERVICE)
    signature = sign(signature, TERMINAL)
    signature = sign(signature, MESSAGE)
    signature_and_version = bytes([VERSION]) + signature
    smtp_password = base64.b64encode(signature_and_version)
    return smtp_password.decode('utf-8')

def main():
    parser = argparse.ArgumentParser(
        description='Convert a Secret Access Key for an IAM user to an SMTP password.')
    parser.add_argument(
        'secret', help='The Secret Access Key to convert.')
    parser.add_argument(
        'region',
        help='The AWS Region where the SMTP password will be used.',
        choices=SMTP_REGIONS)
    args = parser.parse_args()
    print(calculate_key(args.secret, args.region))

if __name__ == '__main__':
    main()

Quindi lanciare il comando:

./aws-smtp-convert.py <IAM-user-Secret-key> <region-name>Modifica questa sezione

Test invio email via PHP

Lo script è basato sulla libreria PHPMAILER ottenibile via composer,

composer require phpmailer/phpmailer

quindi collegando (require) le classi Exception, PHPMailer ed SMTP.

<?php

use PHPMailer\PHPMailer\PHPMailer;
require 'vendor/autoload.php';

require 'vendor/phpmailer/phpmailer/src/Exception.php';
require 'vendor/phpmailer/phpmailer/src/PHPMailer.php';
require 'vendor/phpmailer/phpmailer/src/SMTP.php';

$mail = new PHPMailer;

// SMTP configuration
$mail->isSMTP();

// AWS eu-west-1
$mail->Host     = 'email-smtp.eu-west-1.amazonaws.com';
$mail->SMTPAuth = true;
$mail->Username = '<IAM-user-name>';
$mail->Password = '<password-decodificata-dalla-secret-key-utente-IAM>'; //decoded password see python script @ https://docs.aws.amazon.com/ses/late>
$mail->SMTPSecure = 'tls';
$mail->Port     = 587;

$mail->setFrom('<from-email>', 'Test');
$mail->addReplyTo('<reply-to-email>', 'Test');

// Add a recipient
$mail->addAddress('<receiver-1>');
$mail->addAddress('<receiver-2>');

// Add cc or bcc 
//$mail->addCC('cc@example.com');
//$mail->addBCC('bcc@example.com');

// Email subject
$mail->Subject = 'Send Email via SMTP using PHPMailer';

// Set email format to HTML
$mail->isHTML(true);

// Email body content
$mailContent = '
    <h2>Hello Dude!</h2>
    <p>Here it is a test email</p>';
$mail->Body = $mailContent;

// Send email
if(!$mail->send()){
    echo 'Message could not be sent.';
    echo 'Mailer Error: ' . $mail->ErrorInfo;
}else{
    echo 'Message has been sent';
}