Send Emails with AWS SES

Send Emails with AWS SES

SES (Simple Email Service) is, as the name suggests, a very simple service to enable programmatic access to sending and receiving emails. Here is a simple step by step guide to do that in Python - using the Console, AWS CLI, AWS Boto3, as well as a simple Vanilla Python script.

Choose the one you like and add it to your own application.

Prerequisites

Of course, the first and foremost requirement is, an AWS account. If you are reading this blog, I am sure you already have it. If not, please go ahead and create one. It is absolutely required for any developer today.

Second, you need an email address - different from Gmail. Google does not like us using AWS to generate mails on its behalf. Those mails usually land in spam, or worse, show an ugly security warning. But if you are doing this only for the test, you should not have a problem with that.

Below, I will demonstrate everything using the AWS Web Console, as well as AWS Command Line Interface and the Python Boto3 API. The Web Console does not require any installation. But if you are interested in CLI or Boto3 - I strongly recommend doing that, you should get them installed. Check out these links to do that: CLI, Boto3

Verify Email Addresses

SES takes all precautions to make sure you do not misuse it to spam others. Before you send an email to or from an account using the AWS, you need to "verify" those email addresses. Let us start with the AWS Console

AWS Console

Login to your AWS account from a web browser, and among the list of services, choose the Simple Email Service (SES). Click on that and you will be taken to the SES Home. In the panel on the left, look for Email Addresses. Click on that. There, you will see a button to "Verify a New Email Address".

There you can provide an email ID that you own, and click to Verify. That will generate a simple email to your address. You just have to click a link in that mail and you are done. Similarly, you can verify all the Email ID's that you want to. When you refresh the page, you will see those Email ID's in the list.

AWS CLI

On the CLI, things are absolutely simple. You just need to run a single command and that completes the task for you.

aws ses verify-email-identity --email-address name@domain.com

That is all, it will generate the verification mail to your email id and you can click the link to complete the process.

Boto3

You can also do it in Python - using the Boto3

import boto3

client = boto3.client('ses')
response = client.verify_email_identity(
    EmailAddress='name@domain.com'
)

Simple again. You create an SES client and invoke the verify_email_identity() method on it. It will generate the same email that can allow you to click a link and verify the email account.

Send Email

Now sending an email is simple on either of these interfaces. If you like the Web UI you can do it on the Console, else you can do it on the CLI and also on Boto3.

AWS ensures that you do not misuse the service to generate spam. For this, you are not allowed to generate an email to or from an address that you have not yet verified as your own. You can raise a request to Exit Sandbox. If this is approved, you will be able to send to any Email ID. But, the From is always restricted to the one you have verified.

The From address is frozen - the one you had selected. You can specify your To address. If you have not requested AWS in the SES Sandbox

AWS Console

On the same page in the AWS Console, Email Addresses, select any email address from the list.

When you select it, a button is enabled - to send a test mail. When you click on that, it will open a modal - that allows you to draft your mail.

The From address is not editable - it is the one you chose when you clicked the Sent mail button. You can choose the To address. But again, it has to be within the constraints of SES.

SES allows two formats for sending mails - Raw and Formatted. Formatted is the simplest. You just fill in the required details in each field and click send. That is all. The email is sent!

AWS CLI

Sending an email from the CLI is just a line of code. All you need is the base command with a chunk of options.

aws ses send-email --from sender@domain.com --to receiver@domain.com --text "This is for those who cannot read HTML." --html "<h1>Hello World</h1><p>This is a pretty mail with HTML formatting</p>" --subject "Hello World"

You can have a more complex message text or also specify an HTML message by using the appropriate parameters.

Boto3

A Python script can also use the Boto3 to send a similar email. You just need to create an SES client and invoke the appropriate method.

import boto3

client = boto3.client('ses')
response = client.send_email(
    Destination={
        'ToAddresses': [
            'receiver@domain.com'
        ],
    },
    Message={
        'Body': {
            'Html': {
                'Charset': 'UTF-8',
                'Data': '<h1>Hello World</h1><p>This is a pretty mail with HTML formatting</p>',
            },
            'Text': {
                'Charset': 'UTF-8',
                'Data': 'This is for those who cannot read HTML.',
            },
        },
        'Subject': {
            'Charset': 'UTF-8',
            'Data': 'Hello World',
        },
    },
    Source='sender@domain.com',
)

Raw Mail

In the above examples, we saw some very basic emails. These can be useful - perhaps for some auto-generated notification mails. But in real life, we need a lot more than that.

For this, we use the Raw mails. Here, we can specify and configure a lot details, that can be required while sending an email. If you want real code, you should get into the habit of using the Raw mails and nothing else.

Here is a sample code for sending a raw mail.

import os
import boto3
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication

# Setup the basics here
SENDER = "Sender Name <sender@domain.com>"
RECIPIENT = "recipient@domain.com"
SUBJECT = "Customer service contact info"

# The configuration set used to track mails
CONFIGURATION_SET = "ConfigSet"

# The full path to the file that will be attached to the email.
ATTACHMENT = "path/to/customers-to-contact.xlsx"

# The email body for recipients with non-HTML email clients.
BODY_TEXT = "Hello,\r\nPlease see the attached file for a list of customers to contact."

# The HTML body of the email.
BODY_HTML = """\
<html>
<head></head>
<body>
<h1>Hello!</h1>
<p>Please see the attached file for a list of customers to contact.</p>
</body>
</html>
"""

# The character encoding for the email.
CHARSET = "utf-8"

# Create a new SES resource and specify a region.
client = boto3.client('ses')

# Create an instance of multipart/mixed parent container.
msg = MIMEMultipart('mixed')

# Add subject, from and to lines.
msg['Subject'] = SUBJECT 
msg['From'] = SENDER 
msg['To'] = RECIPIENT

# Create a multipart/alternative child container.
msg_body = MIMEMultipart('alternative')

# Encode the text and HTML content and set the character encoding. This step is
# necessary if you're sending a message with characters outside the ASCII range.
textpart = MIMEText(BODY_TEXT.encode(CHARSET), 'plain', CHARSET)
htmlpart = MIMEText(BODY_HTML.encode(CHARSET), 'html', CHARSET)

# Add the text and HTML parts to the child container.
msg_body.attach(textpart)
msg_body.attach(htmlpart)

# Define the attachment part and encode it using MIMEApplication.
att = MIMEApplication(open(ATTACHMENT, 'rb').read())

# Add a header to tell the email client to treat this part as an attachment,
# and to give the attachment a name.
att.add_header('Content-Disposition','attachment',filename=os.path.basename(ATTACHMENT))

# Attach the multipart/alternative child container to the multipart/mixed
# parent container.
msg.attach(msg_body)

# Add the attachment to the parent container.
msg.attach(att)
#print(msg)
try:
    #Provide the contents of the email.
    response = client.send_raw_email(
        Source=SENDER,
        Destinations=[
            RECIPIENT
        ],
        RawMessage={
            'Data':msg.as_string(),
        },
        ConfigurationSetName=CONFIGURATION_SET
    )
# Display an error if something goes wrong.    
except ClientError as e:
    print(e.response['Error']['Message'])
else:
    print("Email sent! Message ID:"),
    print(response['MessageId'])

Essentially, it instantiates an object msg, and populates all the relevant fields. Finally this is passed into the send_raw_email() method, that actually sends the mail.

SMTP

Well, all that was bound within the boundaries of AWS. We can also go out of AWS limits and still use this service. SES also provides us a simple SMTP interface to send emails using the SES.

This is simple to configure. On the SES console, click on the SMTP settings on the left panel. That will take you to the SMTP configuration page. Click on the button to create your SMTP user and password. The SMTP credentials that it creates, are simply an IAM user - with specific permissions to invoke email requests.

With the user credentials, you can run the below script to send out simple emails.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib

smtphost = "email-smtp.us-east-1.amazonaws.com"
# Get the user name and password - from the IAM user that is created by SES.
password = "BFSNXwvnfnBHmz+dqPAXWgYeH3sfgvfxKbiUR6NvD7oW"
username = "AKIA4VCC4EEK5EXQOGML"
message = "Test from Python via AuthSMTP"

msg = MIMEMultipart()
msg['From'] = "update@thewiz.net"
msg['To'] = "vikas.solegaonkar@gmail.com"
msg['Subject'] = "Test from Python via AuthSMTP"
msg.attach(MIMEText(message, 'plain'))

server = smtplib.SMTP(smtphost)
server.starttls()
server.login(username, password)
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()

The SMTP server is only a convenience utility provided by AWS. Internally, it just maps an incoming SMTP request into the corresponding SES request and then processes it.

There are several feature rich open source SMTP servers, that you can use to create emails with all the functionality you like. Invoking the SMTP send is absolutely simple, and it can be done using your code running anywhere - not restricted to running within AWS.