I tried sending an email from Amazon SES with Python

Trigger

It is necessary to send emails to multiple recipients in the company on a regular basis, and until now, Gmail was sent peacefully with spreadsheet + GAS. But! Specifications have changed in the in-house email sending system! (Error transmission prevention system) As a result, spreadsheet + GAS Gmail sometimes doesn't work well: sob: Wow! Gmail is no good, some other email ... OK, let's go to Amazon SES!

Email sending experiment from Amazon SES with Python

First, SES in Boto3 Official Documentation Check /latest/reference/services/ses.html). Hmmm, it would be nice to use send_email (** kwargs) or send_raw_email (** kwargs). The difference is that you can specify headers and attach files with send_raw_email (** kwargs). On the contrary, send_email (** kwargs) does not correspond to that side, but it is simple. I want to attach a file, but this time I want to implement it quickly so that I can send an email quickly, so let's go with send_email (** kwargs).

First, read ses with Boto3

import boto3

client = boto3.client('ses', 
        aws_access_key_id = accesskey, 
        aws_secret_access_key = secretkey, 
        region_name = region
    )

Send with send_email (** kwargs)

client.send_email(
        Source = '[email protected]',
        Destination = {
            'ToAddresses': [
                '[email protected]',
            ]
        },
        Message = {
            'Subject': {
                'Data': 'Test email',
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': 'Test body',
                    'Charset': 'UTF-8'
                }
            }
        }
    )

Alright, the basics are OK. Next, I will rewrite it to something that can actually be used. Note: The newly created Amazon SES has limited functions, and you need to register the email address to use in advance. For more information, check out the official Frequently Asked Questions About Amazon SES Q: What is the Amazon SES Sandbox? Please give me.

Production implementation

I have confirmed that it can be sent successfully with send_email (** kwargs), so I will implement it so that it can be used in production. First of all, I don't want to embed AWS credentials in the code, so I read it from a CSV file (a file downloaded from IAM).

import csv

def get_credentials():
    with open('Credential CSV file') as f:
        credentials_file = csv.reader(f)
        #Read the second line because the first line of the AWS credential CSV file is the item name
        next(credentials_file)
        for credentials_array in credentials_file:
            return credentials_array

Next, I think that the content of the email should be easy to update, so read it from an external text file. Since I want to separate the email title and the email body, I will set an appropriate goal in the text file as ----- from here the email body -----. Image of mail content file

Test email subject
-----Email body from here-----
Test email body.
def get_mail_content():
    with open('Email content text file') as f:
        mail_content_all = f.read()
    mail_content_splitted = mail_content_all.split('\n-----Email body from here-----\n')
    return mail_content_splitted

Make sure that the recipient's email address is also read from the external CSV file so that it can be easily updated.

def get_addresses():
    with open('CSV file of email address') as f:
        addresses_file = csv.reader(f)
        for addresses_array in addresses_file:
            return addresses_array

Now that you have all the information you need for your email, collect the information you need from the function you created and send it with send_email (** kwargs).

#Get AWS credentials
access_key = get_credentials()[0]
secret_key = get_credentials()[1]

client = boto3.client('ses',
    aws_access_key_id = access_key,
    aws_secret_access_key = secret_key,
    region_name = 'Region used by Amazon SES (No Japan yet)'
)

#Get email content
mail_title = get_mail_content()[0]
mail_body = get_mail_content()[1]

#Get email address
to_addresses = get_addresses()
#Since the email address must be passed as an array[ ]Surround with
cc_addresses = ['CC destination email address']

#send e-mail
client.send_email(
    Source = 'Sender email address',
    Destination = {
        'ToAddresses': to_addresses,
        'CcAddresses': cc_addresses
    },
    Message = {
        'Subject': {
            'Data': mail_title,
            'Charset': 'UTF-8'
        },
        'Body': {
            'Text': {
                'Data': mail_body,
                'Charset': 'UTF-8'
            }
        }
    }
)

This is OK! I thought, but the official Frequently Asked Questions about Amazon SES has this explanation.

Q: Is there a limit to the number of recipients that can be specified in an email message?

You can specify up to 50 recipients for each message you send using Amazon SES. All addresses in the "To:", "CC:", and "BCC:" fields are included in this limit. If you want to send an email message to more than 50 recipients, you must divide the recipient list into groups of 50 or less and send the messages in each group.

By the way, if you exceed 50 people, the following error message will be returned.

botocore.exceptions.ClientError: An error occurred (InvalidParameterValue) when calling the SendEmail operation: Recipient count exceeds 50.

Supports sending by 50 people

I see. It is necessary to process the number of recipients to be sent at one time to 50 or less. First, make the mail sending part a function.

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body):
    #Email sending process
    client.send_email(
        Source = 'Sender email address',
        Destination = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        },
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

If you call this send_email for every 50 recipients

cc_addresses = ['CC destination email address']
to_addresses = get_addresses()
to_addresses_splitted = []
#Loop the number of sets every 50_Assign to count
loop_count = len(to_addresses) // 50 + 1
for i in range(loop_count):
    #Get 50 items (including CC email address) in the set one by one
    for j in range(50 - len(cc_addresses)):
        # send_To of the list of email addresses to pass to email_addresses_Enter email addresses in order in splitted
        try:
            to_addresses_splitted.append(to_addresses[i * 50 + j])
        #Exception handling when a non-existent index is specified
        except:
            pass
    send_email(client, to_addresses_splitted, cc_addresses, mail_title, mail_body)
    #Clear each set
    to_addresses_splitted = []

Oops, all the split emails will be sent to the CC email address. I don't want to send one email to the CC email address at one time, so I will send it to the CC email address only for the first divided transmission.

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body, i):
    #Send to CC address only for the first split
    if 0 == i:
        destination_value = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        }
    else:
        destination_value = {
            'ToAddresses': to_addresses
        }

    #Email sending process
    client.send_email(
        Source = 'Sender email address',
        Destination = destination_value,
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

Summary

import boto3
import csv

def main():
    #AWS information
    access_key = get_credentials()[0]
    secret_key = get_credentials()[1]

    client = boto3.client('ses',
        aws_access_key_id = access_key,
        aws_secret_access_key = secret_key,
        region_name = 'us-east-1'
    )

    #Get email content
    mail_title = get_mail_content()[0]
    mail_body = get_mail_content()[1]

    #Get email address
    cc_addresses = ['CC destination email address']
    to_addresses = get_addresses()
    to_addresses_splitted = []
    #Loop the number of sets every 50_Assign to count
    loop_count = len(to_addresses) // 50 + 1
    for i in range(loop_count):
        #Get 50 items (including CC email address) in the set one by one
        for j in range(50 - len(cc_addresses)):
            # send_To of the list of email addresses to pass to email_addresses_Enter email addresses in order in splitted
            try:
                to_addresses_splitted.append(to_addresses[i * 50 + j])
            #Exception handling when a non-existent index is specified
            except:
                pass
        send_email(client, to_addresses_splitted, cc_addresses, mail_title, mail_body, i)
        #Clear each set
        to_addresses_splitted = []

def get_credentials():
    with open('credentials.csv') as f:
        credentials_file = csv.reader(f)
        #Read the second line because the first line of the AWS credential CSV file is the item name
        next(credentials_file)
        for credentials_array in credentials_file:
            return credentials_array

def get_addresses():
    with open('addresses.csv') as f:
        addresses_file = csv.reader(f)
        for addresses_array in addresses_file:
            return addresses_array

def get_mail_content():
    with open('mail_content.txt') as f:
        mail_content_all = f.read()
    mail_content_splitted = mail_content_all.split('\n---------------------Email body from here---------------------\n')
    return mail_content_splitted

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body, i):
    #Send to CC address only for the first split
    if 0 == i:
        destination_value = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        }
    else:
        destination_value = {
            'ToAddresses': to_addresses
        }

    #Email sending process
    client.send_email(
        Source = 'Sender email address',
        Destination = destination_value,
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

if __name__ == "__main__":
    main()

Now you can send emails locally without worry. However, if it is from the local, other people can not send it (= I will be in charge of sending mail), so I wonder if it will be AWS Lambda soon.

We're hiring! We are developing an AI chatbot. If you are interested, please feel free to contact us from the Wantedly page!

Reference article

Official Boto3 documentation Frequently Asked Questions about Amazon SES

Recommended Posts

I tried sending an email from Amazon SES with Python
I tried sending an email with python.
I tried sending an email with SendGrid + Python
Send an email with Amazon SES + Python
I tried sending an email from the Sakura server with flask-mail
I tried sending an SMS with Twilio
I tried fp-growth with python
I tried scraping with Python
I tried gRPC with Python
I tried scraping with python
I tried to implement an artificial perceptron with python
I tried using the Python library from Ruby with PyCall
[Python] Send an email from gmail with two-step verification set
I tried web scraping with python.
I sent an SMS with Python
Easy email sending with haste python3
I tried running prolog with python 3.8.2.
[Python] Send an email with outlook
I tried SMTP communication with Python
I tried to make an image similarity function with Python + OpenCV
I tried using Amazon SQS with django-celery
I tried scraping Yahoo News with Python
I tried non-photorealistic rendering with Python + opencv
I tried using UnityCloudBuild API from Python
I tried a functional language with Python
I tried recursion with Python ② (Fibonacci sequence)
Scraping from an authenticated site with python
#I tried something like Vlookup with Python # 2
[Data science basics] I tried saving from csv to mysql with python
I tried to send a registration completion email from Gmail with django.
[Outlook] I tried to automatically create a daily report email with Python
I tried hundreds of millions of SQLite with python
I tried "differentiating" the image with Python + OpenCV
Send an email to Spushi's address with python
I want to email from Gmail using Python.
I tried Python! ] I graduated today from "What is Python! Python!"!
I tried L-Chika with Raspberry Pi 4 (Python edition)
I tried Jacobian and partial differential with python
I tried to get CloudWatch data with Python
I tried using mecab with python2.7, ruby2.3, php7
I tried debugging from Python via System Console
I tried function synthesis and curry with python
I tried to output LLVM IR with Python
I tried "binarizing" the image with Python + OpenCV
Generate an insert statement from CSV with Python.
I tried running faiss with python, Go, Rust
I tried to detect an object with M2Det!
I tried to automate sushi making with python
I tried playing mahjong with Python (single mahjong edition)
I tried running Deep Floor Plan with Python 3.6.10.
I tried to start Jupyter with Amazon lightsail
Send an email with Excel attached in Python
I tried to make a simple mail sending application with tkinter of Python
I tried to extract a line art from an image with Deep Learning
[Python] I tried the same calculation as LSTM predict with from scratch [Keras]
I tried Python> autopep8
Validate E-Mail with Python
Send email with Python
I tried Python> decorator
I tried to implement Minesweeper on terminal with python
I tried Amazon Comprehend sentiment analysis with AWS CLI.