Could you please suggest a simple SMTP server with the very basic APIs (by very basic I mean, to read, write, delete email), that could be run on a linux box? I just need to convert the crux of the email into XML format and FTP it to another machine.
9 Answers
Take a look at this SMTP sink server:
from __future__ import print_function
from datetime import datetime
import asyncore
from smtpd import SMTPServer
class EmlServer(SMTPServer):
    no = 0
    def process_message(self, peer, mailfrom, rcpttos, data):
        filename = '%s-%d.eml' % (datetime.now().strftime('%Y%m%d%H%M%S'),
                self.no)
        f = open(filename, 'w')
        f.write(data)
        f.close
        print('%s saved.' % filename)
        self.no += 1
def run():
    # start the smtp server on localhost:1025
    foo = EmlServer(('localhost', 1025), None)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        pass
if __name__ == '__main__':
    run()
It uses smtpd.SMTPServer to dump emails to files.
- 
                    4Thanks, I'm author of SMTP sink server. I just learned from @zero that the command is more simple for test:`sudo /usr/lib/python2.4/smtpd.py -n -c DebuggingServer localhost:25` – twinsant Mar 19 '13 at 07:08
 - 
                    7hmm, tweak it with: `sudo python -m smtpd -c DebuggingServer -n localhost:25` – twinsant Mar 19 '13 at 07:14
 - 
                    it works well when the smtp server/sink and client/sender is on the same machine.. But i am trying a client from another computer of my network (setting the server as the IP of my smtp server) but it does not work.. any idea? I need to open the port somehow? – yeahman Sep 14 '18 at 15:07
 - 
                    @hasen how do you send email from outside to this email server? – Fatemeh Karimi Jul 31 '19 at 12:01
 - 
                    I get a depreciation warning about asyncore and this error: ImportError: cannot import name 'SMTPServer' from partially initialized module 'smtpd' (most likely due to a circular import) (/data/data/com.termux/files/usr/lib/python3.10/smtpd.py) – Brōtsyorfuzthrāx Aug 19 '22 at 13:34
 - 
                    Apparently, the problem was that I named my file email.py, and that conflicted (but the depreciation warning still means the code is going to be broken soon). – Brōtsyorfuzthrāx Aug 19 '22 at 13:50
 
There are really 2 things required to send an email:
- An SMTP Server - This can either be the Python SMTP Server or you can use GMail or your ISP's server. Chances are you don't need to run your own.
 - An SMTP Library - Something that will send an email request to the SMTP server. Python ships with a library called smtplib that can do that for you. There is tons of information on how to use it here: http://docs.python.org/library/smtplib.html
 
For reading, there are two options depending on what server you are reading the email from.
- For a POP Email Server - You can use the poplib python library: http://docs.python.org/library/poplib.html
 - For an IMAP Email Server - You can use the imaplib python library: http://docs.python.org/library/imaplib.html
 
- 25,715
 - 9
 - 65
 - 74
 
To get Hasen's script working in Python 3 I had to tweak it slightly:
from datetime import datetime
import asyncore
from smtpd import SMTPServer
class EmlServer(SMTPServer):
    no = 0
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        filename = '%s-%d.eml' % (datetime.now().strftime('%Y%m%d%H%M%S'),
            self.no)
        print(filename)
        f = open(filename, 'wb')
        f.write(data)
        f.close
        print('%s saved.' % filename)
        self.no += 1
def run():
    EmlServer(('localhost', 25), None)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        pass
if __name__ == '__main__':
    run()
- 6,926
 - 5
 - 62
 - 86
 
A more modern approach is to use the aiosmtpd library (documentation available here).
You can find a good example here: https://aiosmtpd.readthedocs.io/en/latest/controller.html.
Two python smtp servers I've used with success are:
- Twisted's Mail - A very flexible mail library for SMTP, IMAP, ...
 - python-slimta - A complete MTA (smtp relay/forwarding server)
 
Twisted's example is shown below
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
# You can run this module directly with:
#    twistd -ny emailserver.tac
"""
A toy email server.
"""
from __future__ import print_function
from zope.interface import implementer
from twisted.internet import defer
from twisted.mail import smtp
from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
from twisted.cred.portal import IRealm
from twisted.cred.portal import Portal
@implementer(smtp.IMessageDelivery)
class ConsoleMessageDelivery:
    def receivedHeader(self, helo, origin, recipients):
        return "Received: ConsoleMessageDelivery"
    def validateFrom(self, helo, origin):
        # All addresses are accepted
        return origin
    def validateTo(self, user):
        # Only messages directed to the "console" user are accepted.
        if user.dest.local == "console":
            return lambda: ConsoleMessage()
        raise smtp.SMTPBadRcpt(user)
@implementer(smtp.IMessage)
class ConsoleMessage:
    def __init__(self):
        self.lines = []
    def lineReceived(self, line):
        self.lines.append(line)
    def eomReceived(self):
        print("New message received:")
        print("\n".join(self.lines))
        self.lines = None
        return defer.succeed(None)
    def connectionLost(self):
        # There was an error, throw away the stored lines
        self.lines = None
class ConsoleSMTPFactory(smtp.SMTPFactory):
    protocol = smtp.ESMTP
    def __init__(self, *a, **kw):
        smtp.SMTPFactory.__init__(self, *a, **kw)
        self.delivery = ConsoleMessageDelivery()
    def buildProtocol(self, addr):
        p = smtp.SMTPFactory.buildProtocol(self, addr)
        p.delivery = self.delivery
        p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
        return p
@implementer(IRealm)
class SimpleRealm:
    def requestAvatar(self, avatarId, mind, *interfaces):
        if smtp.IMessageDelivery in interfaces:
            return smtp.IMessageDelivery, ConsoleMessageDelivery(), lambda: None
        raise NotImplementedError()
def main():
    from twisted.application import internet
    from twisted.application import service    
    portal = Portal(SimpleRealm())
    checker = InMemoryUsernamePasswordDatabaseDontUse()
    checker.addUser("guest", "password")
    portal.registerChecker(checker)
    a = service.Application("Console SMTP Server")
    internet.TCPServer(2500, ConsoleSMTPFactory(portal)).setServiceParent(a)
    return a
application = main()
- 20,142
 - 3
 - 38
 - 32
 
These are nice examples for a start.
smtpd – Sample SMTP Servers
http://pymotw.com/2/smtpd/index.html
smtplib – Simple Mail Transfer Protocol client
- 7,066
 - 5
 - 39
 - 43
 
I also wanted to start a smtp server in Python and send emails with Python. I wanted to run all this in a Flask web application in a single process, which means the smtp server must be non-blocking. Here's the solution I eventually came to [gist]:
app.py
from flask import Flask, render_template
from smtp_client import send_email
from smtp_server import SMTPServer
app = Flask(__name__)
@app.route('/send_email')
def email():
  server = SMTPServer()
  server.start()
  try:
    send_email()
  finally:
    server.stop()
  return 'OK'
@app.route('/')
def index():
  return 'Woohoo'
if __name__ == '__main__':
  app.run(debug=True, host='0.0.0.0')
smtp_server.py
# smtp_server.py
import smtpd
import asyncore
import threading
class CustomSMTPServer(smtpd.SMTPServer):
  def process_message(self, peer, mailfrom, rcpttos, data):
    print('Receiving message from:', peer)
    print('Message addressed from:', mailfrom)
    print('Message addressed to:', rcpttos)
    print('Message length:', len(data))
    return
class SMTPServer():
  def __init__(self):
    self.port = 1025
  def start(self):
    '''Start listening on self.port'''
    # create an instance of the SMTP server, derived from  asyncore.dispatcher
    self.smtp = CustomSMTPServer(('0.0.0.0', self.port), None)
    # start the asyncore loop, listening for SMTP connection, within a thread
    # timeout parameter is important, otherwise code will block 30 seconds
    # after the smtp channel has been closed
    kwargs = {'timeout':1, 'use_poll': True}
    self.thread = threading.Thread(target=asyncore.loop, kwargs=kwargs)
    self.thread.start()
  def stop(self):
    '''Stop listening to self.port'''
    # close the SMTPserver to ensure no channels connect to asyncore
    self.smtp.close()
    # now it is safe to wait for asyncore.loop() to exit
    self.thread.join()
  # check for emails in a non-blocking way
  def get(self):
    '''Return all emails received so far'''
    return self.smtp.emails
if __name__ == '__main__':
  server = CustomSMTPServer(('0.0.0.0', 1025), None)
  asyncore.loop()
smtp_client.py
import smtplib
import email.utils
from email.mime.text import MIMEText
def send_email():
  sender='author@example.com'
  recipient='6142546977@tmomail.net'
  msg = MIMEText('This is the body of the message.')
  msg['To'] = email.utils.formataddr(('Recipient', recipient))
  msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
  msg['Subject'] = 'Simple test message'
  client = smtplib.SMTP('127.0.0.1', 1025)
  client.set_debuglevel(True) # show communication with the server
  try:
    client.sendmail('author@example.com', [recipient], msg.as_string())
  finally:
    client.quit()
Then start the server with python app.py and in another request simulate a request to /send_email with curl localhost:5000/send_email. Note that to actually send the email (or sms) you'll need to jump through other hoops detailed here: https://blog.codinghorror.com/so-youd-like-to-send-some-email-through-code/.
- 25,611
 - 17
 - 169
 - 224
 
- 
                    Is it possible to receive email from an email account, for example gmail ? If yes how? – blob May 28 '20 at 07:31
 - 
                    @blob check out the codinghorror link above--there are a few steps involved, but this is possible... – duhaime May 28 '20 at 12:03
 
There is Python SMTP server.
This module offers several classes to implement SMTP servers. One is a generic do-nothing implementation, which can be overridden, while the other two offer specific mail-sending strategies.
- 14,034
 - 15
 - 82
 - 102
 
- 3,388
 - 4
 - 26
 - 36
 
- 
                    1Yes,I know.But,I'm unable to figure out how to read email with that lib! A way round this problem, perhaps? – fixxxer Apr 22 '10 at 13:12
 - 
                    1While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Cole Tobin Aug 24 '12 at 00:27
 
If you want to quickly test Django's send_mail with hasen's answer above:
# Skip lines 3 and 4 if not using virtualenv.
# At command prompt
mkdir django1
cd django1
virtualenv venv
source venv/bin/activate
pip install django==1.11
django-admin startproject django1 .
# run the Django shell
python manage.py shell
# paste into shell following:
from django.core.mail import send_mail
send_mail(
    'Subject here',
    'Here is the message.',
    'from@example.com',
    ['to@example.com'],
    fail_silently=False,
)
# This should write an email like the following:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Subject here
From: from@example.com
To: to@example.com
Date: Wed, 02 May 2018 02:12:09 -0000
Message-ID: <20180502021209.32641.51865@i1022>
Here is the message.
Not necessary to have valid values in send_mail function. Above values will work just fine with hasen's example.
- 692
 - 5
 - 12