7

I've been searching for a library or at least functional snippet of code that lets me send an e-mail from Django (or at least in Python) with text content, HTML content, and an ICS calendar attachment that is recognized by every major e-mail client. For my particular use case it's enough if the user is offered an 'add to calendar' button.

I feel like this should be a solved problem by now, but I'm only finding answers that refer to libraries that aren't being maintained or that are outdated or incomplete in some other way. I've tested a couple of snippets that will attach an ICS file, but G-mail doesn't give me the option of adding it to the calendar like it usually does.

Is there a ready made solution that I'm missing?

Divisible by Zero
  • 2,616
  • 28
  • 31
  • 1
    Have you had a look at: https://stackoverflow.com/questions/4823574/sending-meeting-invitations-with-python – Brian Destura Jul 12 '21 at 00:52
  • I have @bdbd, it's out of date, 8 years old, (packages have changed since it was written) and rewriting it with the new packaging, it crashes when I try it, on msg.as_string(). It is also incomplete: for example it creates a ical_atch but it never gets used. I'm referring here to the most upvoted answer from Auberon Vacher – Divisible by Zero Jul 13 '21 at 12:46
  • What's an ics calendar attachment? Don't see why you can't do the first 2 with django's native email module. – Kovy Jacob Jul 14 '21 at 00:28

4 Answers4

5

Start by creating a .ics file, this can ofcourse also be done by a python script to generate dynamic .ics files.

.ics file

More information about iCalendar: basic information and general templates (see example below).

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
BEGIN:VEVENT
UID:uid1@example.com
DTSTAMP:19970714T170000Z
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
DTSTART:19970714T170000Z
DTEND:19970715T035959Z
SUMMARY:Bastille Day Party
GEO:48.85299;2.36885
END:VEVENT
END:VCALENDAR

You can also generate your own iCalendar if you find that easier (see generated example below).

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//ical.marudot.com//iCal Event Maker
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Europe/Berlin
LAST-MODIFIED:20201011T015911Z
TZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20210716T221958Z
UID:20210716T221958Z-901688629@marudot.com
DTSTART;TZID=Europe/Berlin:20210717T120000
DTEND;TZID=Europe/Berlin:20210717T160000
SUMMARY:Stack Overflow
DESCRIPTION:iCalendar example for Stack Overflow user Ciske\n
LOCATION:Amsterdam
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Stack Overflow
TRIGGER:-PT1H
END:VALARM
END:VEVENT
END:VCALENDAR

Django settings.py

EMAIL_HOST = SMTP server.
EMAIL_HOST_USER = Login credentials for the SMTP server.
EMAIL_HOST_PASSWORD = Password credential for the SMTP server.
EMAIL_PORT = SMTP server port.
EMAIL_USE_TLS or _SSL = True if secure connection.

Django views.py

Send email with .ics file as attachment.

from django.core.mail import EmailMessage

# Send email with attachment
email = EmailMessage(
    'Subject',
    'Email body',
    'from@example.com',
    ['to@example.com']
)
email.attach_file('assets/invite.ics', 'text/calendar')
email.send()

You can even add an .html file to your email.

from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string

html_content = render_to_string('assets/email.html', context)

email = EmailMultiAlternatives('Subject', 'Email body', 'from@example.com', [to@example.com])
email.attach_alternative(html_content, 'text/html')
email.attach_file('assets/invite.ics', 'text/calendar')
email.send()
Dharman
  • 30,962
  • 25
  • 85
  • 135
Rutger
  • 195
  • 1
  • 11
  • 1
    I was sure this wouldn't work because I'd tried this already, but trying it again, with your code, it works, at least in Gmail, the calendar attachment is showing up properly with all of the info of the appointment at the top with a possibility to 'add to calendar' with a single click. I haven't tested this yet for other email clients, but, since the bounty period has ended and this was the most helpful answer for what I wanted to do, the bounty is yours. EDIT the difference between your snippet and mine is that I used attach_alternative() instead of attach_file() for the invite. – Divisible by Zero Jul 17 '21 at 19:17
  • Thanks for the bounty! @Ciske I'm happy to hear that it worked in Gmail. Let me know if it works in different email clients. If not we can maybe come up with a different solution. – Rutger Jul 17 '21 at 19:26
1

So the key was to attach the ICS file as a file, not as a string (using django.core.mail.message.EmailMessage.attach_alternative()).

The following snippet works for me in Gmail, Hotmail, and Yahoo mail (MS Outlook to be confirmed), meaning the calendar event information is shown together with the email, and at least Gmail and Hotmail provide an option to add the event to your calendar.

from django.core.mail.message import EmailMultiAlternatives  # At the top of your .py file

email = EmailMultiAlternatives(subject, message, settings.FROM_EMAIL, ['recipient@email.here'])
# email.attach_alternative('<b>html here</b>', 'text/html') # Optional HTML message
email.attach_file(filename_event, 'text/calendar')
email.send(fail_silently=False)

I'm using ics https://pypi.org/project/ics/ to create the ICS file. This package is currently still being maintained. The only other major Python ics file library I could find is ical https://pypi.org/project/icalendar/ and the source for that hasn't been updated in a year as of sep 1st, 2021.

This code works for me to create the ics file:

from ics import Calendar, Event  # At the top of your .py file

ICS_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

calendar = Calendar()
event = Event()
event.name = _("Our event name")
event.begin = appointment.start_time.strftime(ICS_DATETIME_FORMAT)
event.end = appointment.end_time.strftime(ICS_DATETIME_FORMAT)
event.organizer = settings.DEFAULT_FROM_EMAIL
calendar.events.add(event)
filename_event = 'invite-%d.ics' % appointment.id
with open(filename_event, 'w') as ics_file:
    ics_file.writelines(calendar)

where appointment is my own Django class, of which start_time and end_time are of the type DateTimeField.

If you create a new ics file per request, it's important to also have a unique filename per request, so you don't risk two separate requests writing to the file simultaneously.

After sending the ICS file I'll delete it like so:

import os  # At the top of your .py file

os.remove(filename_event)
Divisible by Zero
  • 2,616
  • 28
  • 31
0

Django has built-in solution based on python's smtplib:

https://docs.djangoproject.com/en/3.2/topics/email/

You need to provide some credentials to your smtp server(gmail, mailgun etc) in settings and after that you can use django.core.mail module.

For attaching stuff you can use EmailMessage.attach() or attach_file()

https://docs.djangoproject.com/en/3.2/topics/email/#django.core.mail.EmailMessage

Anton
  • 1
  • 3
  • "Django’s send_mail() and send_mass_mail() functions are actually thin wrappers that make use of the EmailMessage class." So for attaching you 100% need EmailMessage class – Anton Jul 14 '21 at 15:50
  • From the question: """I've tested a couple of snippets that will attach an ICS file, but G-mail doesn't give me the option of adding it to the calendar like it usually does.""" Perhaps I should have stressed that more. That's the key point. I can attach an ics file, but that doesn't mean it will show up correctly in Gmail or Outlook. It seems to be very important exactly how you construct the email message. Like if you're using multiparty/alternative or multipart/mixed and if you're adding the right headers to the calendar part. So ideally I'd like to use a lib that knows exactly how to do it. – Divisible by Zero Jul 14 '21 at 20:58
  • Every "major e-mail client" has own format for ics file linking. You can check here -> https://www.labnol.org/apps/calendar.html 'Calendar links' This guy generates links separately for each email client. So its 90% that 'ready made solution' you want to find - didn't exist. Maybe some clients has their own libs for work with their services (like google-> https://developers.google.com/calendar/api/quickstart/python) But I am pretty sure that solution for ALL major clients did not exists – Anton Jul 15 '21 at 10:04
  • Thanks @Anton for that link about creating links :) I will keep that as a backup option. – Divisible by Zero Jul 15 '21 at 20:25
-1

You can try using mark_safe() it renders the js,html code as a string to a html page . I had used it for customizing Django admin. Check the example below :

some_sample_string = '''<h1>This is some sample </h1>'''
my_sample_html = mark_safe(some_sample_string)

you can design a page in HTML add some custom designing to it and then return the mark_safe byte object to HTML or any web page and tag it there it will work.

you can check these links it might be a help for you

https://www.kite.com/python/docs/django.utils.safestring.mark_safe

Return mark_safe string from __str__

https://www.fullstackpython.com/django-utils-safestring-mark-safe-examples.html

https://docs.djangoproject.com/en/3.0/_modules/django/utils/html/

Jay Jain
  • 59
  • 7