Mail Submission Agent

Step 1: Create the Relay

To get started, we create a Relay object. This is first because Edge depends on Queue and Queue depends on Relay:

from slimta.relay.smtp.mx import MxSmtpRelay

tls_args = {'keyfile': '/path/to/key.pem', 'certfile': '/path/to/cert.pem'}
relay = MxSmtpRelay(tls=tls_args, connect_timeout=20, command_timeout=10,
                                  data_timeout=20, idle_timeout=30)

The result is a variable relay that can be passed in to the Queue constructor. The relay will be capable of opportunistic TLS and will produce transient errors if remote servers take too long to reply.

Step 2: Create the Queue

Now that we have a Relay, we can create the QueueStorage and Queue objects. The simplest QueueStorage sub-class is DictStorage, which stores message contents and meta in dict objects. However, this can be made persistent using shelve:

import shelve
from slimta.queue.dict import DictStorage

env_db = shelve.open('envelope.db')
meta_db = shelve.open('meta.db')
storage = DictStorage(env_db, meta_db)

The resulting storage variable along with relay from Step 1 can create our Queue:

from slimta.queue import Queue

queue = Queue(storage, relay)
queue.start()

Because queue objects inherit gevent.Greenlet, they must call their .start() method to function properly.

Step 3: Add Queue Policies

Now that we have our Queue, we will most likely want to add various QueuePolicy and RelayPolicy rules to affect behavior. MSAs should add various headers, such as Date and Received, for example:

from slimta.policy.headers import *

queue.add_prequeue_policy(AddDateHeader())
queue.add_prequeue_policy(AddMessageIdHeader())
queue.add_prequeue_policy(AddReceivedHeader())

Because our Relay above was MxSmtpRelay, we should also use the RecipientDomainSplit policy. This makes sure that we relay each recipient to its correct domain MX record, since an Envelope can have many recipients of many different domains:

from slimta.policy.split import RecipientDomainSplit

queue.add_prequeue_policy(RecipientDomainSplit())

Most MSAs trust that their clients aren’t going to be sending spam, so we’ll leave discussion of the SpamAssassin policy for the Mail Delivery Agent section. The Forward policy may prove useful in some MSA configurations.

Step 4: Create the Edge

The Edge is how messages are injected into the system from mail clients, and now that we have a Queue object, we can create one. The most common Edge for an MSA will obviously be SmtpEdge, but you could also create an edge service that receives messages by HTTP or even from the filesystem. An SmtpEdge should be created for the RFC-specified port 587 and the deprecated SSL-only port 465.

Creating an Edge can be very simple if you are not worried about how messages are received or from whom:

from slimta.edge.smtp import SmtpEdge

tls_args = {'keyfile': '/path/to/key.pem', 'certfile': '/path/to/cert.pem'}
edge = SmtpEdge(('', 587), queue, tls=tls_args)
edge.start()

This will receive any messages from any sender and send it to the Queue. In our examples, because we chose an MxSmtpRelay, this would also mean our MSA would be an Open Relay. This is BAD! We should add some sort of authentication:

from slimta.smtp.auth import Auth, CredentialsInvalidError

class MyAuth(Auth):
    def verify_secret(self, username, password, identity=None):
        if username != 'testuser' or password != 'testpassword':
            raise CredentialsInvalidError()
        return 'testuser'
    def get_secret(self, username, identity=None):
        if username == 'testuser':
            return 'testpassword', 'testuser'
        raise CredentialsInvalidError()

# Your edge creation line would now look like...
edge = SmtpEdge(('', 587), queue, auth_class=MyAuth, tls=tls_args)
edge.start()

Now, that will allow clients to authenticate, but it will not stop them from sending messages if they haven’t authenticated. For that, we need to add an SmtpValidators:

from slimta.edge.smtp import SmtpValidators

class MyValidators(SmtpValidators):
    def handle_mail(self, reply, sender):
        if not self.session.auth_result:
            reply.code = '550'
            reply.message = '5.7.1 <{0}> Not authenticated'
            return

# Your edge creation line would now look like...
edge = SmtpEdge(('', 587), queue, tls=tls_args,
                validator_class=MyValidators, auth_class=MyAuth)
edge.start()

Your SMTP server will now reject a client’s MAIL FROM:<...> command if they have not first issued a successful AUTH command. If the client cannot issue their MAIL FROM, then the SMTP command sequence cannot continue and they cannot send mail.

Configuring port 465 for SSL-only traffic looks very similar, with one extra keyword argument. Don’t worry, two Edge services may be configured and run simultaneously:

ssl_edge = SmtpEdge(('', 465), queue, tls_tls_args, tls_immediately=True,
                    validator_class=MyValidators, auth_class=MyAuth)
ssl_edge.start()

Step 5: Daemonizing

You probably don’t want to keep a terminal open the entire time your MTA is running. There are some daemonization tools in the slimta.system module. Their usage is relatively simple:

import gevent
from slimta import system

gevent.sleep(0.5)
system.drop_privileges('smtp-user', 'smtp-user')
system.redirect_stdio()  # Redirects all streams to /dev/null by default.
system.daemonize()

The drop_privileges() command is only necessary if you ran your MTA as root, which is necessary to create servers listening on privileged ports such as 25, 587 or 465.

The gevent.sleep() call is not always necessary, but sometimes gevent will not have opened the ports by the time you drop privileges and then it will fail, so calling a short sleep will make sure everything is ready.

Step 6: Profit

Once you have all your pieces together, you can simply let the system function:

try:
    edge.get()
except KeyboardInterrupt:
    pass