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!
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.
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.
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'
}
}
}
)
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!
Official Boto3 documentation Frequently Asked Questions about Amazon SES
Recommended Posts