83

If I want to send mail not via SMTP, but rather via sendmail, is there a library for python that encapsulates this process?

Better yet, is there a good library that abstracts the whole 'sendmail -versus- smtp' choice?

I'll be running this script on a bunch of unix hosts, only some of which are listening on localhost:25; a few of these are part of embedded systems and can't be set up to accept SMTP.

As part of Good Practice, I'd really like to have the library take care of header injection vulnerabilities itself -- so just dumping a string to popen('/usr/bin/sendmail', 'w') is a little closer to the metal than I'd like.

If the answer is 'go write a library,' so be it ;-)

Nate
  • 4,561
  • 2
  • 34
  • 44

8 Answers8

133

Header injection isn't a factor in how you send the mail, it's a factor in how you construct the mail. Check the email package, construct the mail with that, serialise it, and send it to /usr/sbin/sendmail using the subprocess module:

import sys
from email.mime.text import MIMEText
from subprocess import Popen, PIPE


msg = MIMEText("Here is the body of my message")
msg["From"] = "me@example.com"
msg["To"] = "you@example.com"
msg["Subject"] = "This is the subject."
p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE)
# Both Python 2.X and 3.X
p.communicate(msg.as_bytes() if sys.version_info >= (3,0) else msg.as_string()) 

# Python 2.X
p.communicate(msg.as_string())

# Python 3.X
p.communicate(msg.as_bytes())
Nebulosar
  • 1,727
  • 3
  • 20
  • 46
Jim
  • 72,985
  • 14
  • 101
  • 108
  • Perfect. My conceptual problem was separating the construction and sending of email. Thanks! – Nate Sep 16 '08 at 16:16
  • 3
    nice , it helped me today , after 5 years of the answer :) – Peter Apr 08 '13 at 07:23
  • 15
    You should also use the `-oi` parameter to sendmail. This stops a single `.` in your message from prematurely terminating the email. – Robie Basak Jul 01 '13 at 11:34
  • 26
    python3 users should use `.as_bytes()` instead of `as_string()` – ov7a Sep 17 '17 at 12:28
  • I'm confused by the `-oi`. `-o` looks like it's for setting processing options and `-i` looks like it only applies to receiving mail... – DylanYoung Jul 24 '19 at 17:27
  • 1
    @DylanYoung The `-oi` is one flag. From the manpage of `sendmail`: `-oi (default) Do not take dots on a line by themselves as a message terminator.` – IngoMeyer Oct 14 '22 at 17:19
38

This is a simple python function that uses the unix sendmail to deliver a mail.

def sendMail():
    sendmail_location = "/usr/sbin/sendmail" # sendmail location
    p = os.popen("%s -t" % sendmail_location, "w")
    p.write("From: %s\n" % "from@somewhere.com")
    p.write("To: %s\n" % "to@somewhereelse.com")
    p.write("Subject: thesubject\n")
    p.write("\n") # blank line separating headers from body
    p.write("body of the mail")
    status = p.close()
    if status != 0:
           print "Sendmail exit status", status
Pieter
  • 17,435
  • 8
  • 50
  • 89
  • 2
    They specifically said they didn't want `popen`-style solutions. Worse, the reason given was to avoid things like header injection vulnerabilities. If the user supplies either the from or to address, this code is vulnerable to header injection attacks. This is exactly what they didn't want. – Jim Aug 02 '12 at 20:26
  • 8
    @Jim yes, they specifically edited that part in after my answer was given (check edit dates), in response to my answer I suppose. – Pieter Aug 04 '12 at 12:58
  • @Pieter if I have to send attachment? – Hariom Singh Aug 14 '17 at 18:03
12

Jim's answer did not work for me in Python 3.4. I had to add an additional universal_newlines=True argument to subrocess.Popen()

from email.mime.text import MIMEText
from subprocess import Popen, PIPE

msg = MIMEText("Here is the body of my message")
msg["From"] = "me@example.com"
msg["To"] = "you@example.com"
msg["Subject"] = "This is the subject."
p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE, universal_newlines=True)
p.communicate(msg.as_string())

Without the universal_newlines=True I get

TypeError: 'str' does not support the buffer interface
MEI
  • 311
  • 2
  • 5
7

Python 3.5+ version:

import subprocess
from email.message import EmailMessage

def sendEmail(from_addr, to_addrs, msg_subject, msg_body):
    msg = EmailMessage()
    msg.set_content(msg_body)
    msg['From'] = from_addr
    msg['To'] = to_addrs
    msg['Subject'] = msg_subject

    sendmail_location = "/usr/sbin/sendmail"
    subprocess.run([sendmail_location, "-t", "-oi"], input=msg.as_bytes())
Robin Stewart
  • 3,147
  • 20
  • 29
5

This question is very old, but it's worthwhile to note that there is a message construction and e-mail delivery system called Marrow Mailer (previously TurboMail) which has been available since before this message was asked.

It's now being ported to support Python 3 and updated as part of the Marrow suite.

Gert van den Berg
  • 2,448
  • 31
  • 41
amcgregor
  • 1,228
  • 12
  • 29
  • The turboMail link is no go, a year later – TankorSmash May 28 '12 at 00:36
  • 1
    +1 for Marrow Mailer. I tried it out, and it makes it very easy to send mail through smpt, sendmail, etc., and also provides very good validation. Because Marrow Mailer is "a good library that abstracts the whole 'sendmail -versus- smtp' choice", I feel this answers the original question the best. – tjklemz Dec 14 '12 at 19:46
3

It's quite common to just use the sendmail command from Python using os.popen

Personally, for scripts i didn't write myself, I think just using the SMTP-protocol is better, since it wouldn't require installing say an sendmail clone to run on windows.

https://docs.python.org/library/smtplib.html

tovare
  • 4,027
  • 5
  • 29
  • 30
  • 1
    SMTP doesn't work on ~nix boxes using things like [DMA](https://github.com/corecode/dma), which provides sendmail, but does not listen on port 25... – Gert van den Berg Dec 11 '17 at 13:31
-3

I was just searching around for the same thing and found a good example on the Python website: http://docs.python.org/2/library/email-examples.html

From the site mentioned:

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.mime.text import MIMEText

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
fp = open(textfile, 'rb')
# Create a text/plain message
msg = MIMEText(fp.read())
fp.close()

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()

Note that this requires that you have sendmail/mailx set up correctly to accept connections on "localhost". This works on my Mac, Ubuntu and Redhat servers by default, but you may want to double-check if you run into any issues.

elec3647
  • 513
  • 5
  • 8
  • This is still using `localhost` via SMTP protocol. And some users don't want to open port 21, not even on localhost only. – mbirth Jan 26 '17 at 15:20
-7

The easiest answer is the smtplib, you can find docs on it here.

All you need to do is configure your local sendmail to accept connection from localhost, which it probably already does by default. Sure, you're still using SMTP for the transfer, but it's the local sendmail, which is basically the same as using the commandline tool.

user229044
  • 232,980
  • 40
  • 330
  • 338
Frank Wiles
  • 1,589
  • 11
  • 13
  • 7
    Question says "If I want to send mail not via SMTP, but rather via sendmail..." – S19N May 31 '12 at 10:41
  • 2
    Your program will delay in case there are no connection or lag to SMTP server. Using sendmail you pass message to MTA and your program continues. – Denis Barmenkov Oct 12 '12 at 13:20