我正在尝试诊断为什么通过 Amazon SES 发送电子邮件无法通过 python 工作。
以下示例演示了该问题,其中
user
和 pass
设置为适当的凭据。
>>> 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
此消息不是特别有用,并且已尝试其他版本,但似乎无法使其工作。
我可以使用我的雷鸟电子邮件客户端通过这些设置发送电子邮件,所以我的假设是我正在执行与 TLS 相关的任务。
我认为 SMTP_SSL 不再适用于 SES。必须使用starttls()
smtp = smtplib.SMTP("email-smtp.us-east-1.amazonaws.com")
smtp.starttls()
smtp.login(SESSMTPUSERNAME, SESSMTPPASSWORD)
smtp.sendmail(me, you, msg)
完整示例:
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)
我已经确定这个问题是由时间造成的。因为我是从命令行执行该代码,所以服务器会超时。如果我将它放入 python 文件并运行它,它的执行速度足以确保发送消息。
看起来,AWS SES 期望包含数据和所有必要信息的完整周期,并在丢失某些内容的情况下关闭连接。
我刚刚根据官方 AWS SES 文档创建了一个示例,重新格式化以删除一些代码异味并切换到 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)
YouTuber Codegnan 创建了一个很好的演练来设置 SES 和 IAM 以便能够运行上面的代码。
如果您的 SMTP 密码格式正确,上述答案就很好,但如果您只有 AWS 访问密钥,则上述答案不起作用。要将 AWS 访问密钥秘密转换为密码,请参阅 https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html
在此转发:
首先,将以下内容保存到名为
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()
然后,在命令行中运行以下命令:
python path/to/smtp_credentials_generate.py wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY us-east-1
在前面的命令中,执行以下操作:
将
path/to/
替换为您保存 smtp_credentials_generate.py 的位置的路径。
将
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
替换为您要转换为 SMTP 密码的秘密访问密钥。
将
us-east-1
替换为您要在其中使用 SMTP 凭证的 AWS 区域。
当此脚本成功运行时,唯一的输出是您的 SMTP 密码。
您可以将其放入下面的python脚本中进行测试。如果有效,您将不会收到错误。
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')
确保上述脚本中的区域与您正在使用的区域匹配。