Mail Delivery 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
. For this example, we’ll be using
DovecotLdaRelay
, though you could just as easily
substitute one of the other classes in slimta.relay
:
from slimta.relay.pipe import DovecotLdaRelay
relay = DovecotLdaRelay('/usr/lib/dovecot/dovecot-lda', timeout=10.0)
The result is a variable relay
that can be passed in to the Queue
constructor. The relay
will produce transient errors if the command takes
too long to finish.
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. MDAs should add
various headers, such as Date
and Received
, for example:
from slimta.policy.headers import *
queue.add_policy(AddDateHeader())
queue.add_policy(AddMessageIdHeader())
queue.add_policy(AddReceivedHeader())
Because an MDA configuration typically receives mail from the outside world, it
is likely you will want to scan this mail from spam. The
SpamAssassin
policy will add
the X-Spam-Status
set to YES
or NO
for each message, which can be
used by maildrop to filter to a spam quarantine:
from slimta.policy.spamassassin import SpamAssassin
queue.add_policy(SpamAssassin())
As with MSA configurations, MDAs will often find the
Forward
policy useful as well.
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 MDA will obviously be SmtpEdge
, but you could
also create an edge service that receives messages by HTTP or even from the
filesystem. Most MDAs will use SmtpEdge
on the
RFC-specified port 25.
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(('', 25), queue, tls=tls_args)
edge.start()
This will receive any messages from any sender intended for any recipient and
send it to the Queue
. For an MDA however, we only want to accept emails sent
to recipients we host. To do this, we will sub-class
SmtpValidators
and restrict the addresses allowed by
the RCPT TO:<...>
command:
from slimta.edge.smtp import SmtpValidators
class MyValidators(SmtpValidators):
def handle_rcpt(self, reply, recipient, params):
try:
localpart, domain = recipient.rsplit('@', 1)
except ValueError:
reply.code = '550'
reply.message = '5.7.1 <{0}> Not a valid address'
return
if domain.lower() != 'example.com':
reply.code = '550'
reply.message = '5.7.1 <{0}> Not authenticated'
return
# Your edge creation line would now look like...
edge = SmtpEdge(('', 25), queue, tls=tls_args, validator_class=MyValidators)
edge.start()
Your SMTP server will now reject a client’s RCPT TO:<...>
command if the
address is not on the example.com
domain.
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