只有第一个人可以通过Python icalendar获得电子邮件邀请。

huangapple go评论60阅读模式
英文:

Why only the first one can get the email invitation via Python icalendar

问题

这段代码主要用于发送Outlook邀请邮件,但在测试中只有接收者列表中的第一个收件人能够收到邀请。问题可能出在邮件的收件人字段设置上,只有第一个收件人被正确处理,而其他的被忽略了。要修复这个问题,你可以尝试以下步骤:

  1. 更改 msg['To'] 的设置,将接收者列表连接成一个逗号分隔的字符串,而不是分号分隔。修改这一行:

    msg['To'] = ';'.join(self.receivers)
    

    改成:

    msg['To'] = ', '.join(self.receivers)
    

    这会将接收者列表中的所有邮件地址放入同一个 To 字段中,以逗号和空格分隔。

  2. 你的 sendmail 函数调用中,使用 msg["To"] 作为邮件的收件人。确保 self.receivers 也传递给 sendmail 函数,以便每个接收者都会收到邮件。修改这一行:

    self.smtp.sendmail(msg["From"], [msg["To"]], msg.as_string())
    

    改成:

    self.smtp.sendmail(msg["From"], self.receivers, msg.as_string())
    

这些更改应该确保每个邮件地址都会收到邀请。重新运行代码,应该可以发送给接收者列表中的所有人。

英文:

I have a script that will run periodically to send email invitations to all receivers to inform them about upcoming maintenance.
Here is the code example

import os
import uuid
import smtplib
import icalendar
import datetime

from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email import encoders

import pytz
from jinja2 import FileSystemLoader, Environment


class EmailWriter:
    SMTP = 'smtp.test.com'

    def __init__(self, receivers, cluster_name, dtstart=None, dtend=None, available="", tasks=""):
        self.sender = 'sender@test.com'
        self.smtp = smtplib.SMTP(EmailWriter.SMTP)

        self.receivers = receivers
        self.cluster_name = cluster_name
        self.dtstart = dtstart
        self.dtend = dtend
        self.available = available
        self.tasks = tasks

    def __get_email_subject_and_content(self):
        path = os.path.join(os.getcwd(), 'email_templates')
        loader = FileSystemLoader(path)
        env = Environment(loader=loader)
        template_minor = env.get_template('minor_maintenance_email.html')
        template_major = env.get_template('major_maintenance_email.html')
        if 'unavailability' in self.available.lower():
            html_content = template_major.render(
                availability=self.available,
                maintenance_date=self.dtstart,
                start_time=self.dtstart,
                expected_end_time=self.dtend,
                tasks=self.tasks
            )
            subject = '{} | Maintenance | {}'.format(self.cluster_name, self.available)
        else:
            html_content = template_minor.render()
            subject = '{} | Maintenance | 100% Availability'.format(self.cluster_name)
        print('subject : "{}", receivers : "{}", maintenance_date : "{}", start_time : "{}", expected_end_time : "{}", '
              '"task : "{}"'.format(subject, self.receivers, self.dtstart, self.dtstart, self.dtend, self.tasks))
        return subject, html_content

    def __prepare_event(self, subject, content, start, end):
        event = icalendar.Event()
        organizer = icalendar.vCalAddress('MAILTO:' + self.sender)
        event.add('organizer', organizer)
        event.add('status', 'confirmed')
        event.add('category', 'Event')
        event.add('summary', subject)
        event.add('description', content)
        event.add('dtstart', start)
        event.add('dtend', end)
        event.add('dtstamp', datetime.datetime.now())
        event['uid'] = uuid.uuid4()
        # Set the busy status of the appointment to free
        event.add('X-MICROSOFT-CDO-BUSYSTATUS', icalendar.vText('FREE'))
        event.add('priority', 5)
        event.add('sequence', 0)
        event.add('created', datetime.datetime.now())
        for participant in self.receivers:
            attendee = icalendar.vCalAddress('MAILTO:' + participant)
            attendee.params['ROLE'] = icalendar.vText('REQ-PARTICIPANT')
            attendee.params['cn'] = icalendar.vText(' '.join(participant.split('@')[0].split('.')))
            event.add('attendee', attendee, encode=0)
        return event

    def __prepare_alarm(self):
        alarm = icalendar.Alarm()
        alarm.add('action', 'DISPLAY')
        alarm.add('description', 'Reminder')
        # The only way to convince Outlook to do it correctly
        alarm.add('TRIGGER;RELATED=START', '-PT{0}H'.format(1))
        return alarm

    def __prepare_icalendar(self):
        # Build the event itself
        cal = icalendar.Calendar()
        cal.add('prodid', icalendar.vText('-//Calendar Application//'))
        cal.add('version', icalendar.vInt(2.0))
        cal.add('method', icalendar.vText('REQUEST'))
        # creates one instance of the event
        cal.add('X-MS-OLK-FORCEINSPECTOROPEN', icalendar.vBoolean(True))
        return cal

    def __prepare_email_message(self, subject, content):
        # Build the email message
        # msg = MIMEMultipart('alternative')
        msg = MIMEMultipart('mixed')
        msg['Subject'] = subject
        msg['From'] = self.sender
        msg['To'] = ';'.join(self.receivers)
        msg['Content-class'] = 'urn:content-classes:calendarmessage'
        msg.attach(MIMEText(content, 'html', 'utf-8'))
        return msg

    def __prepare_invite_blocker(self, cal):
        filename = 'invite.ics'
        part = MIMEBase('text', 'calendar', method='REQUEST', name=filename)
        part.set_payload(cal.to_ical())
        encoders.encode_base64(part)
        part.add_header('Content-Description', filename)
        part.add_header('Filename', filename)
        part.add_header('Path', filename)
        return part

    def send_appointment(self):
        subject, html_content = self.__get_email_subject_and_content()

        start = datetime.datetime.combine(self.dtstart, datetime.time(0, 0, 0)).astimezone(pytz.UTC)
        end = datetime.datetime.combine(self.dtend, datetime.time(0, 0, 0)).astimezone(pytz.UTC)
        cal = self.__prepare_icalendar()
        event = self.__prepare_event(subject, html_content, start, end)
        alarm = self.__prepare_alarm()

        # Add a reminder
        event.add_component(alarm)
        cal.add_component(event)

        part = self.__prepare_invite_blocker(cal)
        msg = self.__prepare_email_message(subject, html_content)
        msg.attach(part)

        # Send the email out
        self.smtp.sendmail(msg["From"], [msg["To"]], msg.as_string())
        self.smtp.quit()
        print('Invitation sent out')


def main():
    receivers = ['test1@test.com', 'test2@test.com', 'test3@test.com']
    cluster_name = 'TEST NOW (test_now)'  # test cluster name

    email_writer = EmailWriter(
        receivers,
        cluster_name,
        datetime.datetime.strptime('2023-02-16', '%Y-%m-%d').date(),
        datetime.datetime.strptime('2023-02-16', '%Y-%m-%d').date() + datetime.timedelta(days=1),
        '100% Availability',
        tasks='Minor test'
    )
    print('Sending email')
    email_writer.send_appointment()


if __name__ == '__main__':
    main()

However, when I tested the code, I could see only the first recipient in the receivers list can get the outlook invitation.
How to fix the code to let all email account in the list can get the invitation?

答案1

得分: 1

看起来,根据其他示例,msg['To'] 对象需要以逗号分隔的字符串格式存在。我相信你目前使用的是分号;,尝试在你的代码中将它更改为逗号,,看看是否能解决问题。

原始代码:msg['To'] = ';;'.join(self.receivers)

新代码:msg['To'] = ', '.join(self.receivers)

英文:

Looking at some other examples, it looks like the msg['To'] object needs to be in a string format with a delimiter of ',' I believe you are using ';' try changing that in your code and see if that resolves the issue.

current_code: msg['To'] = ';'.join(self.receivers)

new_code: msg['To'] = ', '.join(self.receivers)

答案2

得分: 0

谢谢你的帮助!

最后,我在这里找到了一个解决方案。
首先,就像上面的答案所说,我需要使用作为分隔符。

msg['To'] = ', '.join(self.receivers)

其次,在smtp.sendmail()函数中,接收参数的类型是一个列表,所以这里我直接给函数一个列表

self.smtp.sendmail(msg['From'], self.receivers, msg.as_string())

sendmail()的源代码

    def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
                 rcpt_options=()):
        """This command performs an entire mail transaction.

        The arguments are:
            - to_addrs     : A list of addresses to send this mail to.  A bare
                             string will be treated as a list with 1 address.

 Example:

         >>> import smtplib
         >>> s=smtplib.SMTP("localhost")
         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
         >>> msg = '''\\
         ... From: Me@my.org
         ... Subject: testin'...
         ...
         ... This is a test '''
         >>> s.sendmail("me@my.org",tolist,msg)
英文:

Thank you for your help!

Finally, I got a solution here.
First, as the above answer said, I need to use , as the delimiter.

msg['To'] = ', '.join(self.receivers)

Second, in the function of smtp.sendmail(), the receiver parameter type is a list, so here I directly give the function a list

self.smtp.sendmail(msg['From'], self.receivers, msg.as_string())

Source code of sendmail()

    def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
                 rcpt_options=()):
        """This command performs an entire mail transaction.

        The arguments are:
            - to_addrs     : A list of addresses to send this mail to.  A bare
                             string will be treated as a list with 1 address.

 Example:

         >>> import smtplib
         >>> s=smtplib.SMTP("localhost")
         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
         >>> msg = '''\\
         ... From: Me@my.org
         ... Subject: testin'...
         ...
         ... This is a test '''
         >>> s.sendmail("me@my.org",tolist,msg)

huangapple
  • 本文由 发表于 2023年2月16日 10:42:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467325.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定