Python:连续发送多封电子邮件时出错
Python: Error sending more than one email in a row
我做了一个可以让我发送电子邮件的开发。如果我尝试向一张收据发送电子邮件,则没有问题。但是,如果我尝试将同一封电子邮件发送给多个收据,那么我就会出错。我不想向多个收据发送一封电子邮件,我想要的是向每个收据发送一封电子邮件。
我得到的错误是:
File "/home/josecarlos/Workspace/python/reports/reports/pregame.py", line 22, in __init__
self.send_mail(subject, message, fileName)
File "/home/josecarlos/Workspace/python/reports/reports/report.py", line 48, in send_mail
message = mail.create_message()
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 100, in create_message
message.attach(MIMEText(self.params["message"], "plain"))
File "/usr/lib/python3.6/email/mime/text.py", line 34, in __init__
_text.encode('us-ascii')
AttributeError: 'dict' object has no attribute 'encode'
要发送电子邮件,我有这个 class:
import base64
import logging
import os
import os.path
import pickle
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build
class Mail:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
@staticmethod
def get_service():
"""Gets an authorized Gmail API service instance.
Returns:
An authorized Gmail API service instance..
"""
# If modifying these scopes, delete the file token.pickle.
SCOPES = [
#'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
]
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'com/mail/credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
return service
@staticmethod
def send_message(service, sender, message):
"""Send an email message.
Args:
service: Authorized Gmail API service instance.
sender: User's email address. The special value "me"
can be used to indicate the authenticated user.
message: Message to be sent.
Returns:
Sent Message.
"""
try:
sent_message = (service.users().messages().send(userId=sender, body=message)
.execute())
logging.info('Message Id: %s', sent_message['id'])
return sent_message
except errors.HttpError as error:
logging.error('An HTTP error occurred: %s', error)
def create_message(self):
"""Create a message for an email.
Args:
sender: Email address of the sender.
to: Email address of the receiver.
subject: The subject of the email message.
message_text: The text of the email message.
Returns:
An object containing a base64url encoded email object.
"""
#message = MIMEText(message_text)
message = MIMEMultipart()
message['from'] = self.params["from"]
message['to'] = self.params["to"]
message['subject'] = self.params["subject"]
message.attach(MIMEText(self.params["message"], "plain"))
routeFile = self.params["routeFile"] + self.params["fileName"]
fileName = self.params["fileName"]
# Open PDF file in binary mode
with open(routeFile, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {fileName}",
)
# Add attachment to message and convert message to string
message.attach(part)
s = message.as_string()
b = base64.urlsafe_b64encode(s.encode('utf-8'))
return {'raw': b.decode('utf-8')}
要发送电子邮件,我将使用以下方法:
def send_mail(self, subject, message, fileName):
args = self.params["destiny"]
if self.params["competition"] == COMPETITIONS.LF1 or self.params["competition"] == COMPETITIONS.LF2:
data = SearchData(args, "subscriptors.emails=")
else:
data = SearchDataFIBA(args, "subscriptors.emails=")
emails = data.get_result().getData()
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "basketmetrics@gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : f"output/reports/{self.params['destiny']}/"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "basketmetrics@gmail.com", message)
我的应用程序可以安全访问 google 帐户。
我不知道在没有
的情况下我不能连续发送超过一封电子邮件
仅发送一封电子邮件时出现问题。
我是不是做错了什么?
编辑我:
要重现错误,您可以使用此测试代码进行测试:
import unittest
import os
from com.mail.mail import Mail
class TestSendMail(unittest.TestCase):
def setUp(self) -> None:
os.chdir(os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..')))
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
subject = "Informe pre partido"
fileName = "name_of_the_file"
emails = [{"email" : "receipt1@gmail.com"}, {"email" : "receipt2@gmail.com"}]
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "sender@gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : "route to the file"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "sender@gmail.com", message)
此外,您必须为自己的值和您自己的google帐户
的文件credentials.json文件更改一些值
编辑二:
我找到了它产生错误的地方,但不知道为什么。当我第二次调用 class 邮件时出现问题。在那一刻,我将一些参数传递给带有 params 变量的构造函数。在那个变量中,我传递了消息的文本。此消息是在循环外创建的。
如果我在构造函数的 params["message"] 中读取接收邮件 class 的前 120 个字符:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
print(f"message received: {params['message'][:120]}")
第一次得到message变量的内容:
message received: ¡¡¡Hola!!!
Os enviamos el informe pre partido previo a vuestro próximo partido.
Saludos,
Basketmetrics.com
但是第二次,我应该会收到同样的短信!!!但是我收到一个错误:
Error
Traceback (most recent call last):
File "/usr/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/lib/python3.6/unittest/case.py", line 605, in run
testMethod()
File "/home/josecarlos/Workspace/python/reports/test/test_send_mail.py", line 26, in test_send_mail
mail = Mail(params)
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 27, in __init__
print(f"message received: {params['message'][:120]}")
TypeError: unhashable type: 'slice'
如果我没有任何限制或字符阅读所有消息。我第一次收到可变消息的内容。但是在第二次,我收到了一个非常大的字符串 os 个字符,这里有一个小例子:
lGWFBVZzhQc3pDNWdiNmhnMW1odDZHcmlFZWsyClZvTTBRT1R2dXpWaWlyZkpleS9mZXhtR3V3V2hTV0JjWERtSUNnWENTQVJ1QjdrN1Nzd3BrZ0c1Rkl3MXVDMmNyZk95ZUhySVM1dHQKSUh2T1YvWW1Pd2YzL3B2WEpLaEMza
为什么我收到的是这串字符而不是消息变量的值?
我已经检查过,如果我将消息变量放在 for 循环内,而不是循环外......它有效!!!我收到了这两封邮件!!!
但是,这个解决方案没有用,因为我想重用我的代码并且我需要通过变量传递一些值。
那么,为什么第二次没有收到message变量的值,却收到一长串字符呢?
我该如何解决这个错误?为什么会出现这个错误?
编辑三:
检查我在Mail的构造函数中收到的值的类型,第一次是“string”:
typeof: <class 'str'>
但是第二次是“dict”:
typeof: <class 'dict'>
并检查 self.params["message"] 的键是:
keys: dict_keys(['raw'])
我什么都不懂...pos怎么知道params["message"]有message变量的值,第二次params["message"]修改了它输入原始格式?
编辑四:
我修改了消息变量的内容...
消息=“”
至...
mesage = "¡¡¡Hola!!!0x0a0x0aOs enviamos 之前的党派信息和未来党派的信息。0x0a0x0aSaludos,0x0a0x0aBasketmetrics.com"
但它不起作用。我有同样的错误。
编辑 V:
我修改了消息变量的内容。现在,我将发送 html ...
而不是纯文本
message = """\
<html>
<head></head>
<body>
<p>
Hi, this is a test!!!
</p>
<p>
Best regards!!!
</p>
</body>
</html>
"""
要发送此邮件,您必须在 Mail class 的方法 create_message 中修改此指令:
message.attach(MIMEText(self.params["message"], "plain"))
对此:
message.attach(MIMEText(self.params["消息"], "html"))
还有...我遇到了同样的错误!!!
我不知道该怎么办了...
编辑 VI:
最后一次尝试...我已经修改了消息的文本,删除了“奇怪”字符,如“”或“<”,并且我已经发送了一条包含消息“Hello”的简单文本。
所以,我的消息变量现在是:
message = "Hello"
并且我再次修改了电子邮件的格式,从“html”到“plain”
而且...我在第二封电子邮件中遇到了同样的错误!!!
这真令人沮丧……:((((((
所以最好的解决方案是有一个包含所有电子邮件 ID 的小列表,然后 运行 通过 for 循环,您可以发送邮件,或者您实际上可以使用来自 python 帮助您协助您的工作,
一个简单的例子就是这样
def send_mail(names, emails, senderMail, password, subject):
port = 587
password = password
total = len(names)
server = smtplib.SMTP('smtp.gmail.com', port)
server.starttls()
server.login(senderMail, password)
cc = 0
for i in range(len(emails)):
body = '' #body of the mail, you can actually add html also please check the documentation
msg = MIMEMultipart()
msg['From'] = senderMail
msg['To'] = emails[i]
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
text = msg.as_string()
server.sendmail(senderMail,emails[i], text)
cc += 1
print(cc , " / ", total)
server.quit()
如果你想要一种更简单的附加图片的方法,你也可以查看这个包,
Please check this github repo for detailed work flow
您有:
def send_mail(self, subject, message, fileName):
其中参数 message
是要发送的消息文本。但是我们在你的函数中有 `test_send_mail':
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
# code omitted
for item in emails:
# code omitted
message = mail.create_message() # overlaying message text with return value from create_message
service = mail.get_service()
mail.send_message(service, "sender@gmail.com", message)
在 for item in emails:
循环中,您已经用调用 mail.create_message()
的 return 值覆盖了 message
,因此对于下一次迭代 message
不再是字符串。这个,我相信是你的问题。您需要为调用 mail.create_message()
的 return 值或消息文本使用不同的变量名称。
我做了一个可以让我发送电子邮件的开发。如果我尝试向一张收据发送电子邮件,则没有问题。但是,如果我尝试将同一封电子邮件发送给多个收据,那么我就会出错。我不想向多个收据发送一封电子邮件,我想要的是向每个收据发送一封电子邮件。
我得到的错误是:
File "/home/josecarlos/Workspace/python/reports/reports/pregame.py", line 22, in __init__
self.send_mail(subject, message, fileName)
File "/home/josecarlos/Workspace/python/reports/reports/report.py", line 48, in send_mail
message = mail.create_message()
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 100, in create_message
message.attach(MIMEText(self.params["message"], "plain"))
File "/usr/lib/python3.6/email/mime/text.py", line 34, in __init__
_text.encode('us-ascii')
AttributeError: 'dict' object has no attribute 'encode'
要发送电子邮件,我有这个 class:
import base64
import logging
import os
import os.path
import pickle
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build
class Mail:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
@staticmethod
def get_service():
"""Gets an authorized Gmail API service instance.
Returns:
An authorized Gmail API service instance..
"""
# If modifying these scopes, delete the file token.pickle.
SCOPES = [
#'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
]
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'com/mail/credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
return service
@staticmethod
def send_message(service, sender, message):
"""Send an email message.
Args:
service: Authorized Gmail API service instance.
sender: User's email address. The special value "me"
can be used to indicate the authenticated user.
message: Message to be sent.
Returns:
Sent Message.
"""
try:
sent_message = (service.users().messages().send(userId=sender, body=message)
.execute())
logging.info('Message Id: %s', sent_message['id'])
return sent_message
except errors.HttpError as error:
logging.error('An HTTP error occurred: %s', error)
def create_message(self):
"""Create a message for an email.
Args:
sender: Email address of the sender.
to: Email address of the receiver.
subject: The subject of the email message.
message_text: The text of the email message.
Returns:
An object containing a base64url encoded email object.
"""
#message = MIMEText(message_text)
message = MIMEMultipart()
message['from'] = self.params["from"]
message['to'] = self.params["to"]
message['subject'] = self.params["subject"]
message.attach(MIMEText(self.params["message"], "plain"))
routeFile = self.params["routeFile"] + self.params["fileName"]
fileName = self.params["fileName"]
# Open PDF file in binary mode
with open(routeFile, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {fileName}",
)
# Add attachment to message and convert message to string
message.attach(part)
s = message.as_string()
b = base64.urlsafe_b64encode(s.encode('utf-8'))
return {'raw': b.decode('utf-8')}
要发送电子邮件,我将使用以下方法:
def send_mail(self, subject, message, fileName):
args = self.params["destiny"]
if self.params["competition"] == COMPETITIONS.LF1 or self.params["competition"] == COMPETITIONS.LF2:
data = SearchData(args, "subscriptors.emails=")
else:
data = SearchDataFIBA(args, "subscriptors.emails=")
emails = data.get_result().getData()
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "basketmetrics@gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : f"output/reports/{self.params['destiny']}/"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "basketmetrics@gmail.com", message)
我的应用程序可以安全访问 google 帐户。
我不知道在没有
的情况下我不能连续发送超过一封电子邮件仅发送一封电子邮件时出现问题。
我是不是做错了什么?
编辑我:
要重现错误,您可以使用此测试代码进行测试:
import unittest
import os
from com.mail.mail import Mail
class TestSendMail(unittest.TestCase):
def setUp(self) -> None:
os.chdir(os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..')))
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
subject = "Informe pre partido"
fileName = "name_of_the_file"
emails = [{"email" : "receipt1@gmail.com"}, {"email" : "receipt2@gmail.com"}]
for item in emails:
print(f"Enviamos informe a la cuenta: {item['email']}")
params = {
"from" : "sender@gmail.com",
"to" : item["email"],
"subject": subject,
"message" : message,
"fileName" : fileName,
"routeFile" : "route to the file"
}
mail = Mail(params)
message = mail.create_message()
service = mail.get_service()
mail.send_message(service, "sender@gmail.com", message)
此外,您必须为自己的值和您自己的google帐户
的文件credentials.json文件更改一些值编辑二:
我找到了它产生错误的地方,但不知道为什么。当我第二次调用 class 邮件时出现问题。在那一刻,我将一些参数传递给带有 params 变量的构造函数。在那个变量中,我传递了消息的文本。此消息是在循环外创建的。
如果我在构造函数的 params["message"] 中读取接收邮件 class 的前 120 个字符:
def __init__(self, params):
'''
:param params: It's a dictionary with these keys:
from: Email account from the email is sended
to: Email account who will receive the email
subject: Subject of the email
message: Message of the email.
game: Next games
'''
self.params = params
print(f"message received: {params['message'][:120]}")
第一次得到message变量的内容:
message received: ¡¡¡Hola!!!
Os enviamos el informe pre partido previo a vuestro próximo partido.
Saludos,
Basketmetrics.com
但是第二次,我应该会收到同样的短信!!!但是我收到一个错误:
Error
Traceback (most recent call last):
File "/usr/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/lib/python3.6/unittest/case.py", line 605, in run
testMethod()
File "/home/josecarlos/Workspace/python/reports/test/test_send_mail.py", line 26, in test_send_mail
mail = Mail(params)
File "/home/josecarlos/Workspace/python/reports/com/mail/mail.py", line 27, in __init__
print(f"message received: {params['message'][:120]}")
TypeError: unhashable type: 'slice'
如果我没有任何限制或字符阅读所有消息。我第一次收到可变消息的内容。但是在第二次,我收到了一个非常大的字符串 os 个字符,这里有一个小例子:
lGWFBVZzhQc3pDNWdiNmhnMW1odDZHcmlFZWsyClZvTTBRT1R2dXpWaWlyZkpleS9mZXhtR3V3V2hTV0JjWERtSUNnWENTQVJ1QjdrN1Nzd3BrZ0c1Rkl3MXVDMmNyZk95ZUhySVM1dHQKSUh2T1YvWW1Pd2YzL3B2WEpLaEMza
为什么我收到的是这串字符而不是消息变量的值?
我已经检查过,如果我将消息变量放在 for 循环内,而不是循环外......它有效!!!我收到了这两封邮件!!!
但是,这个解决方案没有用,因为我想重用我的代码并且我需要通过变量传递一些值。
那么,为什么第二次没有收到message变量的值,却收到一长串字符呢?
我该如何解决这个错误?为什么会出现这个错误?
编辑三:
检查我在Mail的构造函数中收到的值的类型,第一次是“string”:
typeof: <class 'str'>
但是第二次是“dict”:
typeof: <class 'dict'>
并检查 self.params["message"] 的键是:
keys: dict_keys(['raw'])
我什么都不懂...pos怎么知道params["message"]有message变量的值,第二次params["message"]修改了它输入原始格式?
编辑四:
我修改了消息变量的内容...
消息=“”
至...
mesage = "¡¡¡Hola!!!0x0a0x0aOs enviamos 之前的党派信息和未来党派的信息。0x0a0x0aSaludos,0x0a0x0aBasketmetrics.com"
但它不起作用。我有同样的错误。
编辑 V:
我修改了消息变量的内容。现在,我将发送 html ...
而不是纯文本 message = """\
<html>
<head></head>
<body>
<p>
Hi, this is a test!!!
</p>
<p>
Best regards!!!
</p>
</body>
</html>
"""
要发送此邮件,您必须在 Mail class 的方法 create_message 中修改此指令:
message.attach(MIMEText(self.params["message"], "plain"))
对此:
message.attach(MIMEText(self.params["消息"], "html"))
还有...我遇到了同样的错误!!!
我不知道该怎么办了...
编辑 VI:
最后一次尝试...我已经修改了消息的文本,删除了“奇怪”字符,如“”或“<”,并且我已经发送了一条包含消息“Hello”的简单文本。
所以,我的消息变量现在是:
message = "Hello"
并且我再次修改了电子邮件的格式,从“html”到“plain”
而且...我在第二封电子邮件中遇到了同样的错误!!!
这真令人沮丧……:((((((
所以最好的解决方案是有一个包含所有电子邮件 ID 的小列表,然后 运行 通过 for 循环,您可以发送邮件,或者您实际上可以使用来自 python 帮助您协助您的工作,
一个简单的例子就是这样
def send_mail(names, emails, senderMail, password, subject):
port = 587
password = password
total = len(names)
server = smtplib.SMTP('smtp.gmail.com', port)
server.starttls()
server.login(senderMail, password)
cc = 0
for i in range(len(emails)):
body = '' #body of the mail, you can actually add html also please check the documentation
msg = MIMEMultipart()
msg['From'] = senderMail
msg['To'] = emails[i]
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
text = msg.as_string()
server.sendmail(senderMail,emails[i], text)
cc += 1
print(cc , " / ", total)
server.quit()
如果你想要一种更简单的附加图片的方法,你也可以查看这个包, Please check this github repo for detailed work flow
您有:
def send_mail(self, subject, message, fileName):
其中参数 message
是要发送的消息文本。但是我们在你的函数中有 `test_send_mail':
def test_send_mail(self):
message = "¡¡¡Hola!!!\n\nOs enviamos el informe pre partido previo a vuestro próximo partido.\n\nSaludos,\n\nBasketmetrics.com"
# code omitted
for item in emails:
# code omitted
message = mail.create_message() # overlaying message text with return value from create_message
service = mail.get_service()
mail.send_message(service, "sender@gmail.com", message)
在 for item in emails:
循环中,您已经用调用 mail.create_message()
的 return 值覆盖了 message
,因此对于下一次迭代 message
不再是字符串。这个,我相信是你的问题。您需要为调用 mail.create_message()
的 return 值或消息文本使用不同的变量名称。