I am trying to build a small SMTP Server through which I can be able to send some messages. Looking at the smtpd library found that there was something. But I only was able to create a server that reads the email received, but never sent it to the address requested.
import smtpd
import asyncore
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
server = CustomSMTPServer(('127.0.0.1', 1025), None)
asyncore.loop()
client:
import smtplib
import email.utils
from email.mime.text import MIMEText
# Create the message
msg = MIMEText('This is the body of the message.')
msg['To'] = email.utils.formataddr(('Recipient', 'recipient#example.com'))
msg['From'] = email.utils.formataddr(('Author', 'author#example.com'))
msg['Subject'] = 'Simple test message'
server = smtplib.SMTP('127.0.0.1', 1025)
server.set_debuglevel(True) # show communication with the server
try:
server.sendmail('author#example.com', ['myadress#gmail.com'], msg.as_string())
finally:
server.quit()
If you really want to do this
then check out the Twisted examples:
http://twistedmatrix.com/documents/current/mail/examples/index.html#auto0
I really don't recommend you write your own MTA (Mail Transfer Agent) as this is
a complex task with many edge cases and standards to have to worry about.
Use an existing MTA such as Postfix, Exim, or Sendmail.
Related
I've written a small smtp server in python (taken from the smtpd docs):
import smtpd
import asyncore
class MySMTPServer(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
server = MySMTPServer(('127.0.0.1', 587), None)
asyncore.loop()
I want to be able to send an email from my gmail account (or whatever email provider) to this server. Here's what I've done so far:
Write an MX record on an A-Level domain
Throw the server up on a box that has port 587 opened
Send an email from gmail to someone#myaleveldomain.com
Profit?
Step 4 hasn't really come through. I can't find any logs that offer any info into what happened, according to my server logs the message from gmail never arrived.
Am I doing something wrong? Please help!
I want to create a SMTP-Gateway that filters emails and redirects them to the remote SMTP server.
from smtpd import SMTPServer
from email.parser import Parser
class SMTPGateway(SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
print('Processing message...')
email = Parser().parsestr(data)
for part in email.walk():
if part.get_content_maintype() == 'text':
text = part.get_payload()
# Process text
# forward email to upstream smtp server
With this code I can receive a message and process it. But I don't know how to forward the message to the remote server.
In my main program, I create the server like this:
localaddress = ('localhost', 3000)
remoteaddress = ('localhost', 9000)
gateway = SMTPGateway(localaddress, remoteaddress)
How can I redirect the message in process_message to the remote server?
The documentation of the SMTP-Server is very short:
https://docs.python.org/2/library/smtpd.html.
I could not find the answer there.
I found the answer myself.
from smtpd import SMTPServer
from email.parser import Parser
class SMTPGateway(SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
print('Processing message...')
email = Parser().parsestr(data)
for part in email.walk():
if part.get_content_maintype() == 'text':
text = part.get_payload()
# Process text
# forward email to upstream smtp server
ip = self._remoteaddr[0]
port = self._remoteaddr[1]
server = SMTP(ip, port)
server.send_message(email)
server.quit()
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Implementing Transport Layer Security in Python - Simple Mail Client
I keep getting this error...
ssl.SSLError: [Errno 185090050] _ssl.c:340: error:0B084002:x509 certificate routines:X509_load_cert_crl_file:system lib
...in a simple mail client assignment which is to open a connection with a google smtp server. The problem almost certainly seems to be in the way I am forming the ssl.wrap_socket() method here....
clientSocket = socket(AF_INET, SOCK_STREAM)
ssl_clientSocket = ssl.wrap_socket(clientSocket,
ca_certs = '/etc/ssl/certs/ca.pm',
cert_reqs = ssl.CERT_REQUIRED)
ssl_clientSocket.connect((mailserver, port))
.. but I can't really figure out exactly what the problem is, and I'm really not sure which .pm file to use in /etc/ssl/certs. I'm using ca.pm because I've seen it done that way in a few tutorials. Here is the remainder of the code for good measure....
from socket import *
import certifi
import ssl
msg = "\r\n I love computer networks!"
endmsg = "\r\n.\r\n"
# Choose a mail server (e.g. Google mail server) and call it mailserver
mailserver = "smtp.gmail.com"
port = 587
# Create socket called clientSocket and establish a TCP connection with mailserver
clientSocket = socket(AF_INET, SOCK_STREAM)
ssl_clientSocket = ssl.wrap_socket(clientSocket,
ca_certs = '/etc/ssl/certs/ca.pm',
cert_reqs = ssl.CERT_REQUIRED)
ssl_clientSocket.connect((mailserver, port))
###################################################################
print "got to here 1"
###############################################################
recv = ssl_clientSocket.recv(1024)
print
print recv
# If the first three numbers of what we receive from the SMTP server are not
# '220', we have a problem
if recv[:3] != '220':
print '220 reply not received from server.'
# Send HELO command and print server response.
heloCommand = 'HELO Alice\r\n'
ssl_clientSocket.send(heloCommand)
recv1 = ssl_clientSocket.recv(1024)
print recv1
######################################################################
print "Got to here 2"
#####################################################################
# If the first three numbers of the response from the server are not
# '250', we have a problem
if recv1[:3] != '250':
print '250 reply not received from server.'
# Send MAIL FROM command and print server response.
mailFromCommand = 'MAIL From: wgimson#gmail.com\r\n'
ssl_clientSocket.send(mailFromCommand)
recv2 = ssl_clientSocket.recv(1024)
print recv2
# If the first three numbers of the response from the server are not
# '250', we have a problem
if recv2[:3] != '250':
print '250 reply not received from server.'
# Send RCPT TO command and print server response.
rcptToCommand = 'RCPT To: macyali#gmail.com\r\n'
ssl_clientSocket.send(rcptToCommand)
recv3 = ssl_clientSocket.recv(1024)
print recv3
# If the first three numbers of the response from the server are not
# '250', we have a problem
if recv3[:3] != '250':
print '250 reply not received from server.'
# Send DATA command and print server response.
dataCommand = 'DATA\r\n'
ssl_clientSocket.send(dataCommand)
recv4 = ssl_clientSocket.recv(1024)
print recv4
# If the first three numbers of the response from the server are not
# '250', we have a problem
if recv4[:3] != '250':
print '250 reply not received from server.'
# Send message data.
ssl_clientSocket.send(msg)
# Message ends with a single period.
ssl_clientSocket.send(endmsg)
# Send QUIT command and get server response.
quitCommand = 'QUIT\r\n'
ssl_clientSocket.send(quitCommand)
recv5 = ssl_clientSocket.recv(I1024)
print recv5
# If the first three numbers of the response from the server are not
# '250', we have a problem
if recv5[:3] != '221':
print '221 reply not received from server.'
the error might be caused by a typo: '/etc/ssl/certs/ca.pm' -> '/etc/ssl/certs/ca.pem'
i.e., the system can't find ca.pm file
'/etc/ssl/certs/ca.pem' doesn't contain necessary CA certs.
You could download cacert.pem from http://curl.haxx.se/ca/cacert.pem (it is more current) or use ca_certs = certifi.where() (I see import certifi in your code), example:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import smtplib
import socket
import ssl
import sys
from email.header import Header
from email.mime.text import MIMEText
from getpass import getpass
import certifi # $ pip install certifi
login, password = 'user#gmail.com', getpass('Gmail password:')
ca_certs = certifi.where()
##ca_certs = 'cacert.pem' # $ wget http://curl.haxx.se/ca/cacert.pem
class SMTP_SSL(smtplib.SMTP_SSL):
"""Add support for additional ssl options."""
def __init__(self, host, port=0, **kwargs):
self.ssl_kwargs = kwargs.pop('ssl_kwargs', {})
self.ssl_kwargs['keyfile'] = kwargs.pop('keyfile', None)
self.ssl_kwargs['certfile'] = kwargs.pop('certfile', None)
smtplib.SMTP_SSL.__init__(self, host, port, **kwargs)
def _get_socket(self, host, port, timeout):
if self.debuglevel > 0:
print>>sys.stderr, 'connect:', (host, port)
new_socket = socket.create_connection((host, port), timeout)
new_socket = ssl.wrap_socket(new_socket, **self.ssl_kwargs)
self.file = smtplib.SSLFakeFile(new_socket)
return new_socket
# create message
msg = MIMEText('message body…', _charset='utf-8')
msg['Subject'] = Header('subject…', 'utf-8')
msg['From'] = login
msg['To'] = login
# send it via gmail
s = SMTP_SSL('smtp.gmail.com', timeout=10,
ssl_kwargs=dict(cert_reqs=ssl.CERT_REQUIRED,
ssl_version=ssl.PROTOCOL_SSLv3,
ca_certs=ca_certs))
s.set_debuglevel(1) # enable debug output
try:
s.login(login, password)
s.sendmail(msg['From'], msg['To'], msg.as_string())
finally:
s.quit()
I am using smtplib and I am sending notification emails from my application. However I noticed that sometimes (especially when there is a lot of idle time between mail sending) I get a SMTPServerDisconnected error.
I guess there are 2 solutions for this (know none of them, though)
Increase idle time between sending mails
reconnect when connection is down.
I think 2nd solution seems more elegant. But how can I do that?
edit: I am adding the code
from smtplib import SMTP
smtp = SMTP()
smtp.connect('smtp.server.com')
smtp.login('username','password')
def notifyUser():
smtp.sendmail(from_email, to_email, msg.as_string())
Yes, you can check if the connection is open. To do this, issue a NOOP command and test for status == 250. If not, then open the connection before sending out your mail.
def test_conn_open(conn):
try:
status = conn.noop()[0]
except: # smtplib.SMTPServerDisconnected
status = -1
return True if status == 250 else False
def send_email(conn, from_email, to_email, msg):
if not test_conn_open(conn):
conn = create_conn()
conn.sendmail(from_email, to_email, msg.as_string())
return conn # as you want are trying to reuse it.
Note that you are doing this as opening a connection, say with gmail, consumes time, like 2-3 secs. Subsequently, to optimize on sending multiple emails that you may have at hand, then you should also follow Pedro's response (last part).
If your use case is sending a single message at a time, the solution that seems most correct to me, would be to create a new SMTP session for each message:
from smtplib import SMTP
smtp = SMTP('smtp.server.com')
def notifyUser(smtp, smtp_user, smtp_password, from_email, to_email, msg):
smtp.login(smtp_user, smtp_password)
smtp.sendmail(from_email, to_email, msg.as_string())
smtp.quit()
If your SMTP server doesn't required that you authenticate yourself (a common case), this can be further simplified to:
from smtplib import SMTP
smtp = SMTP('smtp.server.com')
def notifyUser(smtp, from_email, to_email, msg):
smtp.sendmail(from_email, to_email, msg.as_string())
smtp.quit()
If it is common to have more than one message to send at once, and you want to optimise this case by reusing the same SMTP session for the group of messages (can be simplified as above if you don't need to login to the SMTP server):
from smtplib import SMTP
smtp = SMTP('smtp.server.com')
def notifyUsers(smtp, smtp_user, smtp_password, from_to_msgs):
"""
:param from_to_msgs: iterable of tuples with `(from_email, to_email, msg)`
"""
smtp.login(smtp_user, smtp_password)
for from_email, to_email, msg in from_to_msgs:
smtp.sendmail(from_email, to_email, msg.as_string())
smtp.quit()
I'd like to emit my own message when someone connects to this smtp server.
import smtpd
import asyncore
class FakeSMTPServer(smtpd.SMTPServer):
__version__ = 'TEST EMAIL SERVER'
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
if __name__ == "__main__":
smtp_server = FakeSMTPServer(('localhost', 25), None)
try:
asyncore.loop()
except KeyboardInterrupt:
smtp_server.close()
However, I am still getting the response:
220 Win7-PC Python SMTP proxy version 0.2
How do I override the welcome message in python to see "TEST EMAIL SERVER"?
Just do
smtpd.__version__ = "TEST EMAIL SERVER"
somewhere (may be after import statements).