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 值或消息文本使用不同的变量名称。