Random   •   Archives   •   RSS   •   About   •   Contact

Quickstart to DKIM Sign Email with Python

For a long time I have put off DKIM signing email sent from my web services because I couldn't wrap my head around all the indirection Postfix requires to make it work.

Honestly, I put it off for over 5 years...

Today a thought sprung into my head:

"Could I sign Email at the application level before passing to Postfix?"

As you may know, I primarily use the Python programming language, so I did some research and found a reference to a single library called dkimpy (previously pydkim). The codebase started over 10 years ago and appeared stable and mature.

The part that sold me was that dkimpy seemed compatible with the two Python standard library modules which I already use:

  • email which I use to prepare messages
  • smtplib which I use to transport messages to Postfix running on localhost

One issue I did have with dkimpy was the complete lack of examples or even a quickstart guide.

For this reason, I have written this post!

The Missing dkimpy Quickstart Guide

  1. To install dkimpy you may use pip (requirements.txt) or in my case I added it to my setup.py.

  2. Generate a public / private keypair. Don't let this step trip you up, the process easy. In a Unix -like environment you may run the following commands to create the keys.

    generate private key (I name my file after the domain and DKIM selector I plan to use).

    openssl genrsa -out remarkbox.com.20180605.pem 1024
    

    generate public key from the private key.

    openssl rsa -in remarkbox.com.20180605.pem -out remarkbox.com.20180605.pub -pubout
    
  3. Install the public key (.pub) as a DNS TXT record, where the record name ("selector") is 20180605._domainkey and the value body is:

    v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcplYPRsqIFwXuggtH2XgQDMX+e+6sGnWdV8ld/FR9zgRAxB+DeiCEVooVvYt2JRZUEokgDFvys82Q+JTbN4qHNz19bdcBGrnTsnIFaQYpgeQYmPLdDtcWRKzTYMRNCnRmmEXyGv7WIDcaTapIq9NFgLmy1QT7ZTxuNjQtDB/2LwIDAQAB;
    

    You may choose any selector, I happen to like to use YearMonthDay. Additionally you will substitute your public key in place of mine. Put each the line of the public key on a single line in the TXT record.

  4. On each of my application servers I store my private portion of my DKIM key in /etc/dkim/remarkbox.com.20180605.pem. You may store your key any where on the filesystem that is accessible to the user or group running the application.

  5. This is how I used to send Email with Python:

    import smtplib
    
    from email.mime.multipart import MIMEMultipart
    
    from email.mime.text import MIMEText
    
    # catch socket errors when postfix isn't running...
    from socket import error as socket_error
    
    
    def send_email(
        to_email,
        sender_email,
        subject,
        message_text,
        message_html,
        relay="localhost"
    ):
        msg = MIMEMultipart("alternative")
        msg.attach(MIMEText(message_text, "plain"))
        msg.attach(MIMEText(message_html, "html"))
        msg["Subject"] = subject
        msg["From"] = sender_email
        msg["To"] = to_email
        # TODO: react if connecting to postfix is a socket error.
        s = smtplib.SMTP(relay)
        s.sendmail(sender_email, [to_email], msg.as_string())
        s.quit()
        return msg
    
  6. This is how I now send DKIM signed Email with Python:

    import dkim
    
    import smtplib
    
    from email.mime.multipart import MIMEMultipart
    
    from email.mime.text import MIMEText
    
    # catch socket errors when postfix isn't running...
    from socket import error as socket_error
    
    def send_email(
        to_email,
        sender_email,
        subject,
        message_text,
        message_html,
        relay="localhost",
        dkim_private_key_path="",
        dkim_selector="",
    ):
        sender_domain = sender_email.split("@")[-1]
        msg = MIMEMultipart("alternative")
        msg.attach(MIMEText(message_text, "plain"))
        msg.attach(MIMEText(message_html, "html"))
        msg["To"] = to_email
        msg["From"] = sender_email
        msg["Subject"] = subject
    
        if dkim_private_key_path and dkim_selector:
            with open(dkim_private_key_path) as fh:
                dkim_private_key = fh.read()
            headers = ["To", "From", "Subject"]
            sig = dkim.sign(
                message=msg.as_string(),
                selector=str(dkim_selector),
                domain=sender_domain,
                privkey=dkim_private_key,
                include_headers=headers,
            )
            msg["DKIM-Signature"] = sig.lstrip("DKIM-Signature: ")
    
        # TODO: react if connecting to postfix is a socket error.
        s = smtplib.SMTP(relay)
        s.sendmail(sender_email, [to_email], msg.as_string())
        s.quit()
        return msg
    

Like always, if you have any questions feel free to leave a comment or contact me.




Looking for a better comment system?

You should try Remarkbox — a hosted comment service that embeds in your pages to keep the conversation in the same place as your content. It works everywhere, even static sites!

Remarks: Quickstart to DKIM Sign Email with Python

© Russell Ballestrini.