Reputation: 5097
I am trying to diagnose why sending email through Amazon SES is not working via python.
The following example demonstrates the problem, where user
and pass
are set to the appropriate credentials.
>>> import smtplib
>>> s = smtplib.SMTP_SSL("email-smtp.us-east-1.amazonaws.com", 465)
>>> s.login(user, pw)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/smtplib.py", line 549, in login
self.ehlo_or_helo_if_needed()
File "/usr/lib/python2.6/smtplib.py", line 510, in ehlo_or_helo_if_needed
(code, resp) = self.helo()
File "/usr/lib/python2.6/smtplib.py", line 372, in helo
(code,msg)=self.getreply()
File "/usr/lib/python2.6/smtplib.py", line 340, in getreply
raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed
This message is not particularly useful, and have tried other vraiations, but can't seem to get it to work.
I can send email using my thunderbird email client with these settings, so my assumption is that I am mission something TLS-related.
Upvotes: 15
Views: 20487
Reputation: 1476
The above answers are great if you have the SMTP password formatted properly, but don't work if you just have the AWS access key secret. To convert the AWS access key secret into a password, see https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html
Reposting here:
First, save the following to a file named smtp_credentials_generate.py
#!/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)
"eu-south-1", # Europe (Milan)
"eu-north-1", # Europe (Stockholm)
"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 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()
Then, at the command line, run the following command:
python path/to/smtp_credentials_generate.py wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY us-east-1
In the preceding command, do the following:
Replace path/to/
with the path to the location where you saved smtp_credentials_generate.py.
Replace wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
with the secret access key that you want to convert into an SMTP password.
Replace us-east-1
with the AWS Region in which you want to use the SMTP credentials.
When this script runs successfully, the only output is your SMTP password.
You can put it in the below python script to test it. If it works, you will not get an error.
import smtplib
smtp = smtplib.SMTP("email-smtp.us-west-2.amazonaws.com")
smtp.starttls()
smtp.login('access key id goes here', 'smtp password goes here')
Make sure the region in the above script matches the region you are using.
Upvotes: 0
Reputation: 1595
It looks like, AWS SES expects a full cycle with data and all the necessary information and closes the connection in case something is missing.
I just created an example based on the officially AWS SES documentation, reformatted to remove some code smells and switched to SMTP_SLL:
from email.utils import formataddr
from smtplib import SMTP_SSL, SMTPException
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Replace [email protected] with your "From" address.
# This address must be verified.
SENDER = '[email protected]'
SENDERNAME = 'Sender Name'
# Replace [email protected] with a "To" address. If your account
# is still in the sandbox, this address must be verified.
RECIPIENT = '[email protected]'
# Replace smtp_username with your Amazon SES SMTP user name.
USERNAME_SMTP = "AWS_SES_SMTP_USER"
# Replace smtp_password with your Amazon SES SMTP password.
PASSWORD_SMTP = "AWS_SES_SMTP_PWD"
# (Optional) the name of a configuration set to use for this message.
# If you comment out this line, you also need to remove or comment out
# the "X-SES-CONFIGURATION-SET:" header below.
# CONFIGURATION_SET = "ConfigSet"
# If you're using Amazon SES in an AWS Region other than US West (Oregon),
# replace email-smtp.us-west-2.amazonaws.com with the Amazon SES SMTP
# endpoint in the appropriate region.
HOST = "email-smtp.us-west-2.amazonaws.com"
PORT = 465
# The subject line of the email.
SUBJECT = 'Amazon SES Test (Python smtplib)'
# The email body for recipients with non-HTML email clients.
BODY_TEXT = ("Amazon SES Test - SSL\r\n"
"This email was sent through the Amazon SES SMTP "
"Interface using the Python smtplib package.")
# The HTML body of the email.
BODY_HTML = """<html>
<head></head>
<body>
<h1>Amazon SES SMTP Email Test - SSL</h1>
<p>This email was sent with Amazon SES using the
<a href='https://www.python.org/'>Python</a>
<a href='https://docs.python.org/3/library/smtplib.html'>
smtplib</a> library.</p>
</body>
</html>"""
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = SUBJECT
msg['From'] = formataddr((SENDERNAME, SENDER))
msg['To'] = RECIPIENT
# Comment or delete the next line if you are not using a configuration set
# msg.add_header('X-SES-CONFIGURATION-SET',CONFIGURATION_SET)
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(BODY_TEXT, 'plain')
part2 = MIMEText(BODY_HTML, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Try to send the message.
try:
with SMTP_SSL(HOST, PORT) as server:
server.login(USERNAME_SMTP, PASSWORD_SMTP)
server.sendmail(SENDER, RECIPIENT, msg.as_string())
server.close()
print("Email sent!")
except SMTPException as e:
print("Error: ", e)
The YouTuber Codegnan created a nice walkthrough to set up SES and IAM to be able to run the code above.
Upvotes: 6
Reputation: 17928
I don't think SMTP_SSL works anymore with SES. One must use starttls()
smtp = smtplib.SMTP("email-smtp.us-east-1.amazonaws.com")
smtp.starttls()
smtp.login(SESSMTPUSERNAME, SESSMTPPASSWORD)
smtp.sendmail(me, you, msg)
Upvotes: 13
Reputation: 691
A full example:
import smtplib
user = ''
pw = ''
host = 'email-smtp.us-east-1.amazonaws.com'
port = 465
me = u'[email protected]'
you = ('[email protected]',)
body = 'Test'
msg = ("From: %s\r\nTo: %s\r\n\r\n"
% (me, ", ".join(you)))
msg = msg + body
s = smtplib.SMTP_SSL(host, port, 'yourdomain')
s.set_debuglevel(1)
s.login(user, pw)
s.sendmail(me, you, msg)
Upvotes: 8
Reputation: 5097
I have determined this problem to be caused by the timing. Because I was executing that code from the command line, the server would timeout. If I put it into a python file and run it, it executes fast enough to ensure the message is sent.
Upvotes: 6