Sign emails with DKIM

DKIM is short for DomainKeys Identified Mail. It is a method for associating a domain name to an email message. It allows a person, role, or organization (domain owner) to claim some responsibility for the message.

DKIM adds a digital signature to the email message headers (using DKIM-Signature field), which can be validated by recipients. Signer also puts his/hers public key in the TXT DNS record. The verifier recovers the signer’s public key from the specified DNS record, and then verifies that the signature matches the message’s content.

Usually DKIM signature covers several most important message headers (From:, Subject:) and the message body.

The DKIM-Signature header field apart from the actual signature signature contains the domain name, the list of covered header fields, the signing algorithm, and the method by which text snippets are simplified for signing purposes (canonicalization).

Create private/public key for DKIM

The easiest way to generate public and private key for DKIM purposes is to use OpenSSL. The output is already Base64 encoded and ready to be used when createing DNS record and signing an email.

To create a private key:
openssl genrsa -out private.key 1024

To create a public key using the private key:
openssl rsa -in private.key -pubout -out public.key

Full listing follows:


c:\>openssl genrsa -out dkim_private.key 1024

Loading 'screen' into random state - done
Generating RSA private key, 1024 bit long modulus
...++++++
..++++++
e is 65537 (0x10001)

c:\>openssl rsa -in dkim_private.key -pubout -out dkim_public.key
writing RSA key

c:\>

Create DNS record for DKIM

Copy your public key from dkim_public.key file. Copy everything between “—–BEGIN PUBLIC KEY—–” and “—–END PUBLIC KEY—–” and remove new lines:


-----BEGIN PUBLIC KEY-----
MIG...AQAB
-----END PUBLIC KEY-----

Simplest DKIM DNS record has following format:

"v=DKIM1; p=MIG...AQAB; t=s"

  • v – is DKIM version (must be DKIM1)
  • p – is your public key
  • t=s – specifies that domain does not send mail using any subdomains
    • You can create such record manually or use many online DKIM DNS Record Creation Tools.

      Now choose your selector name. It can be any string, it can identify departments or even individual users. In this example we’ll use “alpha” as our selector name.

      The name of the DKIM TXT record is created as follows:

      selector + "._domainkey"

      e.g.: alpha._domainkey

      Now you’ll need to add this record to your DNS.

      Send DKIM signed message

      Sending DKIM signed messages is simple using Mail.dll .NET email component. First we’ll use PemReader class to create RSACryptoServiceProvider from the private key stored on disk in pem format. Then we’ll use Smtp class to connect and authenticate to our SMTP server and send the email message:

      // C#
      
      RSACryptoServiceProvider rsa = new PemReader().ReadPrivateKeyFromFile(@"d:\dkim_private.key");
      
      IMail email = Limilabs.Mail.Fluent.Mail.Text("text")
             .From("alice@example.com")
             .To("bob@mail.com")
             .Subject("subject")
             .DKIMSign(rsa, "alpha", "example.com")
             .Create();
      
      using(Smtp smtp = new Smtp())
      {
          smtp.Connect("smtp.example.com");  // or ConnectSSL for SSL
          smtp.UseBestLogin("alice@example.com", "password");
          smtp.SendMessage(email);                     
          smtp.Close();   
      } 
      
      ' VB.NET
      
      Dim rsa As RSACryptoServiceProvider = New PemReader().ReadPrivateKeyFromFile("d:\dkim_private.key")
      
      Dim email As IMail = Mail _
      		.Text("text") _
      		.From("alice@example.com") _
      		.[To]("bob@mail.com") _
      		.Subject("subject") _
      		.DKIMSign(rsa, "alpha", "example.com") _
      		.Create()
      
      Using smtp As New Smtp()
      	smtp.Connect("smtp.example.com")
      	' or ConnectSSL for SSL
      	smtp.UseBestLogin("alice@example.com", "password")
      	smtp.SendMessage(email)
      	smtp.Close()
      End Using
      

      Verify DKIM signed message

      Mail.dll .NET email component automatically queries DNS and validates DKIM signed messages:

      // C#
      
      var eml = ...;
      IMail email = new MailBuilder().CreateFromEml(eml);
      if (email.IsDKIMSigned)
      {
          bool isValid = email.CheckDKIMSignature();
      }
      
      ' VB.NET
      
      Dim eml = ...
      Dim email As IMail = New MailBuilder().CreateFromEml(eml)
      If email.IsDKIMSigned Then
          Dim isValid As Boolean = email.CheckDKIMSignature()
      End If
      

      You can use .NET IMAP component to receive messages from the IMAP server.

      Verify DKIM signed message – details

      Under the hood the recipient of the email queries the DNS server for TXT record for selector._domainkey.domainName (in our example it is: “alpha._domainkey.example.com”). Selector and domain name are stored inside the DKIM-Signature: email header. From the DNS record public key is extracted:

      C:\>nslookup
      Default Server:  UnKnown
      Address:  192.168.0.1
      
      > set type=TXT
      > alpha._domainkey.example.com
      Server:  UnKnown
      Address:  192.168.0.1
      
      Non-authoritative answer:
      alpha._domainkey.example.com      text =
      
              "v=DKIM1; p=MIG...AQAB; t=s"
      
      ...
      >
      

      As you can see we (as a receiver) get that same record we (as a sender) set in our DNS, now the recipient can use the p= parameter to get the public key and create RSACryptoServiceProvider . It can be later used to verify the signature.

      You can download .NET email component here.

Receive unseen emails using POP3

If you want to receive unseen emails only, you must know, that this is not possible using POP3 protocol. There is no way to mark which message was received by the client application (such as Mail.dll POP3 component) on the POP3 server. POP3 servers simply don’t store such information.

You have to mark which message has been read by yourself on the client application.

UIDL command is very helpful in achieving this goal. UIDL command returns unique-id for every message in the mailbox. You can save this id (in a custom file for example) for every message you have received. You can learn more about unique IDs used in POP3 protocol.

The following example downloads email messages from POP3 server and parses them. You should provide your own WasReceived method implementation which checks if specified id was already processed and stores this id if it wasn’t.

// C# version

using System;
using Limilabs.Mail;
using Limilabs.Client.POP3;

class Program
{
    static void Main(string[] args)
    {
        using (Pop3 pop3 = new Pop3())
        {
            pop3.Connect("pop3.example.com");
            pop3.Login("user", "password");

            MailBuilder builder = new MailBuilder();
            foreach (string uid in pop3.GetAll())
            {
                Console.WriteLine("Message unique-id: {0};", uid);

                if (WasReceived(uid) == true)
                   continue;

                IMail email = builder.CreateFromEml(
                    pop3.GetMessageByUID(uid));

                Console.WriteLine(email.Subject);
                Console.WriteLine(email.Text);
            }
            pop3.Close();
        }
    }

    private static bool WasReceived(string uid)
    {
        // Here you should check if this uid was already received.
        // Store this uid for future reference if it wasn't.
        throw new NotImplementedException();
    }
}
' VB.NET version


Imports System
Imports Limilabs.Mail
Imports Limilabs.Client.POP3

Public Module Module1
    Public Sub Main(ByVal args As String())

        Using pop3 As New Pop3()
            pop3.Connect("pop3.example.com")
            pop3.Login("user", "password")

            Dim builder As New MailBuilder()
            For Each uid As String In pop3.GetAll()
                Console.WriteLine("Message unique-id: {0};", uid)

                if (WasReceived(uid) == true)
                   continue;

                Dim email As IMail = builder.CreateFromEml(pop3.GetMessageByUID(uid))

                Console.WriteLine(email.Subject)
                Console.WriteLine(email.Text)
            Next
            pop3.Close()
        End Using

    End Sub


    Private Shared Function WasReceived(uid As String) As Boolean
        ' Here you should check if this uid was already received.
        ' Store this uid for future reference if it wasn't.
	Throw New NotImplementedException()
    End Function
End Module

VERP (Variable Envelope Return Path) in .NET

Variable Envelope Return Path (VERP) is a technique used to enable automatic detection and removal of undeliverable e-mail addresses. It works by using a different return path (also called “envelope sender” or MAIL FROM) for each recipient of a message.

Bounce message processing problem

In general the hard part of bounce handling is matching up a bounce message with the undeliverable address that caused the bounce.

Mail.dll email library includes Bounce class that analyses bounced emails and extracts undeliverable Recipient address. The problem is that some bounced messages don’t contain this information or use invalid or unrecognizable format.

If the mailing software could see that a bounce resulted from an attempt to send a message to bob@example.org, then it doesn’t need to understand the rest of the information in the bounce. It can simply count how many messages were recently sent to this email address, and how many bounces resulted, and if the proportion of bounced messages is too high, the address is removed from the list.

There’s no need to process bounces with VERP

VERP solves bounce analyzing problem, by using a different return path for each email.

Consider an email send from wikipedians@wiki.net to bob@example.org.

VERP changes the sender address to
wikipedians+bob=example.org@wiki.net – it is still a valid email address, but it also contains information about the original recipient of the email.

If the email is bounced, bounce message comes to a unique email address (wikipedians+bob=example.org@wiki.net) that also contains information about the undeliverable recipient address (bob@example.org).

This way by creating catch all mailbox you can easily filter bounced email addresses.

Return-path email header

Return-path email header contains email address that delivery failures (bounces) should go to.

Return-path is added at the receiving end, from the SMTP’s “MAIL FROM” command. That’s why it is not the correct place for the VERP address, as in most cases it will be ignored and/or overridden by real SMTP envelope sender (“MAIL FROM”).

Proper VERP message

So how should the proper VERP message look like? Here are some points:

  • It has From email header set to original sender:
    wikipedians@wiki.net.
  • It has To email header set to original recipient:
    bob@example.org.
  • It uses special, unique MAIL FROM address during SMTP conversation:
    wikipedians+bob=example.org@wiki.net.
  • It has Return-path email header set to this unique address (wikipedians+bob=example.org@wiki.net), usually as a result of setting this header with MAIL FROM value.

System.Net.Mail does not work

As Return-path header is usually replaced by “MAIL FROM” command .NET System.Net.Mail.SmtpClient class can not be used to create proper VERP messages. You can only set the from address, which System.Net.Mail will also use as the “MAIL FROM” during SMTP conversation.

Setting different Return-path does not help, as it is overridden.

Sending VERP messages in .NET

Sending VERP email messages with Mail.dll is extremely easy. You just need to use SmtpMail.CreateUsingVERP method to create SmtpMail object that can be sent using Smtp client. Simple as that:

// C#

MailBuilder builder = new MailBuilder();
builder.Subject = @"Subject";
builder.Html = @"Body";
builder.From.Add(new MailBox("wikipedians@wiki.net"));
builder.To.Add(new MailBox("bob@example.org"));
IMail email = builder.Create();

SmtpMail smtpMail = SmtpMail.CreateUsingVERP(email);

using (Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.wiki.net");
    smtp.UseBestLogin("user", "password");
    smtp.SendMessage(smtpMail);
    smtp.Close();
}
' VB.NET

Dim builder As MailBuilder = New MailBuilder()
builder.Subject = "Subject"
builder.Html = "Body"
builder.From.Add(New MailBox("wikipedians@wiki.net"))
builder.[To].Add(New MailBox("bob@example.org"))
Dim email As IMail = builder.Create()

Dim smtpMail As SmtpMail = SmtpMail.CreateUsingVERP(email)

Using smtp As New Smtp()
	smtp.Connect("smtp.wiki.net")
	smtp.UseBestLogin("user", "password")
	smtp.SendMessage(smtpMail)
	smtp.Close()
End Using

Processing bounced messages

Mail.dll email library also contains a useful tool for processing VERP addresses:

// C#

VERPAddress verp = VERPAddress.Parse(
    "wikipedians+bob=example.org@wiki.net");

Console.WriteLine(verp.ToAddress);    // "bob@example.org"
Console.WriteLine(verp.FromAddress);  // "wikipedians@wiki.net"
' VB.NET

Dim verp As VERPAddress = VERPAddress.Parse( _
    "wikipedians+bob=example.org@wiki.net")

Console.WriteLine(verp.ToAddress)    ' "bob@example.org"
Console.WriteLine(verp.FromAddress)  ' "wikipedians@wiki.net"

You need to use IMAP or POP3 component to receive bounced messages.

// C#


using(Imap imap = new Imap())
{
    imap.Connect("imap.example.com");   // or ConnectSSL for SSL
    imap.Login("wikipedians@wiki.net", "password");

    imap.SelectInbox();

    foreach (long uid in imap.Search(Flag.Unseen))
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);

        // wikipedians+bob=example.org@wiki.net
        VERPAddress verp = VERPAddress.Parse(email.To[0]); 

        Console.WriteLine(verp.ToAddress); // "bob@example.org"
    }
    imap.Close();
}
' VB.NET

Using imap As New Imap()
    imap.Connect("imap.example.com")	' or ConnectSSL for SSL
    imap.Login("wikipedians@wiki.net", "password")

    imap.SelectInbox()

    For Each uid As Long In imap.Search(Flag.Unseen)
        Dim eml = imap.GetMessageByUID(uid)
	Dim email As IMail = New MailBuilder().CreateFromEml(eml)

	' wikipedians+bob=example.org@wiki.net
	Dim verp As VERPAddress = VERPAddress.Parse(email.[To](0))

	Console.WriteLine(verp.ToAddress)	' "bob@example.org"
    Next
    imap.Close()
End Using

VERP drawbacks

The only drawback of this methods is, that email messages must be sent one by one. Even if the actual message is the same, “undisclosed recipients and many BCC addresses” technique can not be used.

This is because of a limitation of SMTP, which allows multiple recipient addresses to be specified in a single transaction, but only one sender address. And each VERP message must have its own unique MAIL FROM address specified during SMTP conversation.

In most cases this is not a serious problem as most newsletters are personalized this days, at least with the simplest “Hi [FirstName] [LastName]” email template.

Specify ‘MAIL FROM’ address manually

Mail.dll email component is very flexible and you can always specify ‘MAIL FROM’ address directly:

// C#

IMail email = ...

SmtpMail smtpMail = CreateFrom(email);
smtpMail.From = "wikipedians+bob=example.org@wiki.net";

using (Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.wiki.net");
    smtp.UseBestLogin("user", "password");
    smtp.SendMessage(smtpMail);
    smtp.Close();
}
' VB.NET

Dim email As IMail = ...

Dim smtpMail As SmtpMail = CreateFrom(email)
smtpMail.From = "wikipedians+bob=example.org@wiki.net"

Using smtp As New Smtp()
	smtp.Connect("smtp.wiki.net")
	smtp.UseBestLogin("user", "password")
	smtp.SendMessage(smtpMail)
	smtp.Close()
End Using

This might be useful, if you want to add additional information, besides the original recipient email address, such as mailing id.

Unblock .dll file

Symptoms

You can not add .dll file as a reference in Visual Studio or SecurityException is thrown.

Solution

Most likely the problem is a protection on files coming from other computers. You just have to open file properties and click on Unblock button. Unblock the zip file first, and then extract the dll, unblock the dll if needed:

Other tricks is to copy the file to a file system that doesn’t support alternate data streams, that slices them off the file. A flash drive for example.

.chm file is not displayed correctly

Symptoms

When you open a .chm file, “Navigation to the webpage was canceled” is displayed in the reading pane.

Solution

Most likely the problem is a protection on files coming from other computers. You just have to open file properties and click on Unblock button: