modified: .gitignore
new file: content/2018-06-04-quickstart-to-dkim-sign-email-with-python.rst
This commit is contained in:
parent
d2b1b2d39d
commit
226eaa0f1e
2 changed files with 147 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -13,6 +13,7 @@ __pycache__/
|
|||
.Python
|
||||
env/
|
||||
build/
|
||||
build/*
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
|
|
|
|||
146
content/2018-06-04-quickstart-to-dkim-sign-email-with-python.rst
Normal file
146
content/2018-06-04-quickstart-to-dkim-sign-email-with-python.rst
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
Quickstart to DKIM Sign Email with Python
|
||||
################################################################
|
||||
|
||||
:author: Russell Ballestrini
|
||||
:slug: quickstart-to-dkim-sign-email-with-python
|
||||
:date: 2018-06-04 09:03
|
||||
:tags: Code
|
||||
:status: published
|
||||
|
||||
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).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openssl genrsa -out remarkbox.com.20180605.pem 1024
|
||||
|
||||
generate public key from the private key.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
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 you application.
|
||||
|
||||
5. This is how I used to send Email with Python:
|
||||
|
||||
.. code-block:: 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:
|
||||
|
||||
.. code-block:: 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue