Sending Outlook appointments with Python
By andre
Python allows you to send calendar appointments (invitations / events) directly from your code. It is quite easy to create a new appointment in the standard iCalendar format (ics). You can do it by hand or use a convenient icalendar open source module. In order to convince Outlook, however, to present the iCalendar events as native calendar appointments, you will need to make some effort. Fortunately, other people have already collected all the necessary pieces. Below is a working example for sending Outlook-friendly invitation from Python. In principle, you could use some of the information here to send similar invitation using different API, for example .NET.
Note, that you need to supply the relevant timezone information with your dates, otherwise the result might be different from what you expect.
import email.MIMEText
import email.MIMEBase
from email.MIMEMultipart import MIMEMultipart
import smtplib
import datetime as dt
import icalendar
import pytz
# Imagine this function is part of a class which provides the necessary config data
def send_appointment(self, attendee_email, organiser_email, subj, description, location, start_hour, start_minute):
# Timezone to use for our dates - change as needed
tz = pytz.timezone("Europe/London")
start = tz.localize(dt.datetime.combine(self.date, dt.time(start_hour, start_minute, 0)))
# Build the event itself
cal = icalendar.Calendar()
cal.add('prodid', '-//My calendar application//example.com//')
cal.add('version', '2.0')
cal.add('method', "REQUEST")
event = icalendar.Event()
event.add('attendee', attendee_email)
event.add('organizer', organiser_email)
event.add('status', "confirmed")
event.add('category', "Event")
event.add('summary', subj)
event.add('description', description)
event.add('location', location)
event.add('dtstart', start)
event.add('dtend', tz.localize(dt.datetime.combine(self.date, dt.time(start_hour + 1, start_minute, 0))))
event.add('dtstamp', tz.localize(dt.datetime.combine(self.date, dt.time(6, 0, 0))))
event['uid'] = self.get_unique_id() # Generate some unique ID
event.add('priority', 5)
event.add('sequence', 1)
event.add('created', tz.localize(dt.datetime.now()))
# Add a reminder
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(reminder_hours))
event.add_component(alarm)
cal.add_component(event)
# Build the email message and attach the event to it
msg = MIMEMultipart("alternative")
msg["Subject"] = subj
msg["From"] = organiser_email
msg["To"] = atendee_email
msg["Content-class"] = "urn:content-classes:calendarmessage"
msg.attach(email.MIMEText.MIMEText(description))
filename = "invite.ics"
part = email.MIMEBase.MIMEBase('text', "calendar", method="REQUEST", name=filename)
part.set_payload( cal.to_ical() )
email.Encoders.encode_base64(part)
part.add_header('Content-Description', filename)
part.add_header("Content-class", "urn:content-classes:calendarmessage")
part.add_header("Filename", filename)
part.add_header("Path", filename)
msg.attach(part)
# Send the email out
s = smtplib.SMTP('localhost')
s.sendmail(msg["From"], [msg["To"]], msg.as_string())
s.quit()
Cancelling an existing event
You can send a cancel for an existing appointment as well. In order to do this, change “method” in both places in the code above from REQUEST to CANCEL, and set status to CANCELLED. You must ensure you use the same id as you used to create your original event.
No-response events
If you don’t want the appointment participant’s Outlook to send you a reply when he or she accepts, you can use the following code:
event.add("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=FALSE", email)
I hope these code snippets and guidelines will prove helpful in your automation endeavours :)