Hunter Ford Hunter Ford

HTML Emails with Inline Images in Django

For an upcoming party I was having, I decided to create an e-vite like application to send out invitations and allow people to respond via the site. I wanted to include an image in the email, and there just isn't a built-in utility inside Django. So I decided to extend the EmailMultiAlternatives class. Most of the code should be pretty self-explanatory. Also attached is an example of invocation.

import os.path
import re

from email.MIMEBase import MIMEBase

from django.conf import settings
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart

class EmailMultiRelated(EmailMultiAlternatives):
    """
    A version of EmailMessage that makes it easy to send multipart/related
    messages. For example, including text and HTML versions with inline images.
    """
    related_subtype = 'related'

    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
            connection=None, attachments=None, headers=None, alternatives=None):
        # self.related_ids = []
        self.related_attachments = []
        return super(EmailMultiRelated, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers, alternatives)

    def attach_related(self, filename=None, content=None, mimetype=None):
        """
        Attaches a file with the given filename and content. The filename can
        be omitted and the mimetype is guessed, if not provided.

        If the first parameter is a MIMEBase subclass it is inserted directly
        into the resulting message attachments.
        """
        if isinstance(filename, MIMEBase):
            assert content == mimetype == None
            self.related_attachments.append(filename)
        else:
            assert content is not None
            self.related_attachments.append((filename, content, mimetype))

    def attach_related_file(self, path, mimetype=None):
        """Attaches a file from the filesystem."""
        filename = os.path.basename(path)
        content = open(path, 'rb').read()
        self.attach_related(filename, content, mimetype)

    def _create_message(self, msg):
        return self._create_attachments(self._create_related_attachments(self._create_alternatives(msg)))

    def _create_alternatives(self, msg):
        for i, (content, mimetype) in enumerate(self.alternatives):
            if mimetype == 'text/html':
                for filename, _, _ in self.related_attachments:
                    content = re.sub(r'(?<!cid:)%s' % re.escape(filename), 'cid:%s' % filename, content)
                self.alternatives[i] = (content, mimetype)

        return super(EmailMultiRelated, self)._create_alternatives(msg)

    def _create_related_attachments(self, msg):
        encoding = self.encoding or settings.DEFAULT_CHARSET
        if self.related_attachments:
            body_msg = msg
            msg = SafeMIMEMultipart(_subtype=self.related_subtype, encoding=encoding)
            if self.body:
                msg.attach(body_msg)
            for related in self.related_attachments:
                msg.attach(self._create_related_attachment(*related))
        return msg

    def _create_related_attachment(self, filename, content, mimetype=None):
        """
        Convert the filename, content, mimetype triple into a MIME attachment
        object. Adjust headers to use Content-ID where applicable.
        Taken from http://code.djangoproject.com/ticket/4771
        """
        attachment = super(EmailMultiRelated, self)._create_attachment(filename, content, mimetype)
        if filename:
            mimetype = attachment['Content-Type']
            del(attachment['Content-Type'])
            del(attachment['Content-Disposition'])
            attachment.add_header('Content-Disposition', 'inline', filename=filename)
            attachment.add_header('Content-Type', mimetype, name=filename)
            attachment.add_header('Content-ID', '<%s>' % filename)
        return attachment

Invocation

msg = EmailMultiRelated('Subject', 'Plain text version', 'John Foo <john@foo.com>', ['Jane Bar <jane@bar.com'])

html = '<html><body><p>This is my nicely <strong>formatted</strong> message. <a href="mailto:john@foo.com">Email</a> me back.</p><img src="inline.jpg"></body></html'

msg.attach_alternative(html, 'text/html')

for image in event.attachment_set.all():
    msg.attach_related_file(image.file.path)

ics_data = '''BEGIN:VCALENDAR
PRODID:-//HunterFord//EN
VERSION:2.0
BEGIN:VEVENT
URL:http://example.com
DTSTART:20101001T200000
DTEND:20101001T235959
SUMMARY:Fall is in the Air
ORGANIZER;CN=John Foo:MAILTO:john@foo.com
LOCATION:Home
DESCRIPTION:
PRIORITY:3
END:VEVENT
END:VCALENDAR'''

msg.attach('event.ics', ics_data, 'text/calendar')
msg.send()

Growl Notifications for Apple Mail on Mac OS X

UPDATE: You can grab a beta version of 64-bit Growl 1.2 from the Growl beta site.
GrowlMail is broken on Mac OS X Snow Leopard (10.6). The dev team for Growl has a fix in the works, but I use Prowl and like to have notifications go to my iPhone when I'm not at my computer. So in the meantime, here is my solution.

Screen shot 2009-09-02 at 9.51.22 AM

I was originally inspired by James Higgs.

You first want to create a new AppleScript using AppleScript Editor and save it somewhere logical. I used /Library/Scripts/Mail Scripts/Rule Actions/Growl.scpt Below is the code:

-- Growl Alerts in Mail
-- Hunter Ford [http://www.cupcakewithsprinkles.com]
-- This script arises from the lack of any Growl Support in Mac OS X Snow Leopard (10.6)
-- Code inspired by and adapted from James Higgs [http://blog.jameshiggs.com/2009/08/28/growlmail-on-snow-leopard-a-temporary-fix/] as well as those mentioned.

tell application "GrowlHelperApp"
	-- Make a list of all the notification types
	-- that this script will ever send:
	set the allNotificationsList to {"New Email"}

	-- Make a list of the notifications
	-- that will be enabled by default.
	-- Those not enabled by default can be enabled later
	-- in the 'Applications' tab of the growl prefpane.
	set the enabledNotificationsList to {"New Email"}

	-- Register our script with growl.
	-- You can optionally (as here) set a default icon
	-- for this script's notifications.
	register as application "Mail" all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Mail"
end tell

-- Mail Rule Trigger
--
-- Source: Benjamin S. Waldie [http://www.mactech.com/articles/mactech/Vol.21/21.09/ScriptingMail/index.html]
using terms from application "Mail"
	on perform mail action with messages theSelectedMessages for rule theRule
		repeat with thisMessage in theSelectedMessages
			-- Process the current message

			-- Grab the subject and sender of the message
			set growlSubject to subject of thisMessage
			set growlSender to my ExtractName(sender of thisMessage)

			-- Use the first 100 characters of a message
			set growlMessage to (content of thisMessage)
			set growlLength to (length of growlMessage)

			if growlLength > 100 then
				set growlMessage to (characters 1 through 100 of growlMessage) & "…"
			end if

			set growlMessage to growlSubject & "

" & growlMessage

			-- Send a Notification
			tell application "GrowlHelperApp"
				notify with name "New Email" title growlSender description growlMessage application name "Mail"
			end tell
		end repeat
	end perform mail action with messages
end using terms from

-- *ExtractName*
--
-- gathers the name portion from the "From: " line
--
-- Source: robJ [http://forums.macosxhints.com/archive/index.php/t-19954.html]
to ExtractName(sender_)
	if sender_ begins with "<" then
		return text 2 thru -2 of sender_
	else
		set oldTIDs to text item delimiters
		try
			set text item delimiters to "<"
			set name_ to first text item of sender_
			set text item delimiters to oldTIDs
		on error
			set text item delimiters to oldTIDs
		end try
		return name_
	end if
end ExtractName

Run this first in the AppleScript Editor. As this is what will "register" the notification with Growl. Click the green "play" button that says "Run".

Screen shot 2009-09-01 at 10.46.18 PM

Then you want to create a new rule in mail that will run this script on every message that comes in.

Screen shot 2009-09-01 at 4.45.20 PM

Make sure to select "Don't Apply" that way you don't flood your screen with tons of Growl notifications.

Screen shot 2009-09-01 at 4.53.15 PM

Digitally Signed E-Mails with Thawte Personal E-Mail Certificates

UPDATE: Thawte is doing away with Free Personal E-Mail Certificates. You can read their FAQ for more information.

Digitally signed and encrypted e-mails have been around for a long time. Unfortunately, it's never really taken off. Thawte hopes to change all that with its Web of Trust. They offer free certificates for personal use.

The biggest problem to date in my experience has been e-mail clients ability to handle digitally signed and encrypted e-mails. I use a Apple Mail to handle all of communications since its support for IMAP is top-notch.

To give you a little background, digital signatures are apart of the S/MIME specification. Here's how Wikipedia defines it.

S/MIME provides the following cryptographic security services for electronic messaging applications: authentication, message integrity and non-repudiation of origin (using digital signatures) and privacy and data security (using encryption). S/MIME specifies the application/pkcs7-mime (smime-type "enveloped-data") type for data enveloping (encrypting): the whole (prepared) MIME entity to be enveloped is encrypted and packed into an object which subsequently is inserted into an application/pkcs7-mime MIME entity.

If you want to get started using digital signature in your e-mail, you need to first sign-up with Thawte. At the bottom of the page, click the link that says "Click here". You'll then have to proceed through the wizard, verify your email, and request a certificate. After your certificate has been generated, you'll download it, and it should get added to your keychain.

After it's been added to your keychain, you need to make one adjustment so that you can use it in Mail. You'll want to double-click the certificate you just added so that you get a new pane. Then, click "Trust" to expand it. Then you need to tell it to "Always Trust" "When using this certificate".

Screen shot 2009-08-19 at 4.01.10 PM

The next time you open a composition window in Mail, you'll see a few new buttons whenever you compose an email from the email address you got the certificate for.

Screen shot 2009-08-19 at 4.11.14 PM

The following is from Mail's help:

A Signed icon (containing a checkmark) in the lower-right side of the message header indicates the message will be signed when you send it.

An Encrypt (closed lock) icon appears next to the Signed icon if you have a personal certificate for a recipient in your keychain; the icon indicates the message will be encrypted when you send it.

You can add several emails to your Thawte account, but each one will need its own certificate.

Now you're halfway there. You need to become "Trusted" so that your name can be put in the certificate. Upon doing this, and downloading new certificates, your recipients won't be presented with the message: "Unable to verify message signature". So in order to become trusted, your identity must be verified by a Web of Trust notary. Go to the website and login. You'll have to locate a few notaries in your area that will verify your information. Each notary can assign a certain number of "trust points". You need 50 trust points in order to be trusted. Each notary can give out between 10-35 trust points. You'll have to meet this person face-to-face and present them with a few documents (driver's license, passport, company photo ID) and copies of those documents for them to keep. Before you're meeting you'll have to share your details with them through the website.