Skip to content

Instantly share code, notes, and snippets.

@jabofh
Last active March 31, 2021 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jabofh/13f7b2ef36c273fc7a773ff7abcc7ce2 to your computer and use it in GitHub Desktop.
Save jabofh/13f7b2ef36c273fc7a773ff7abcc7ce2 to your computer and use it in GitHub Desktop.
Python wrapper around mail to allow easy attachments
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Python 3.2+ Script to send the contents of a file (or files) as a MIME message.
"""
import os, sys, pwd
import textwrap
import smtplib
# For guessing MIME type based on file name extension
import mimetypes
from argparse import ArgumentParser
import argparse
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
COMMASPACE = ', '
debugflag = False
version = '1.0a'
def debug(message, marker = ""):
if debugflag:
import os, sys
if marker != "":
marker = "[" + os.path.basename(__file__).split(".")[0] + "|" + marker + "]: "
print(marker + str(message), file = sys.stderr)
def getemail(email):
import os, sys, pwd
from socket import gethostname
if email == '':
email = pwd.getpwuid(os.geteuid()).pw_name + '@' + gethostname()
if len(email.split('@')) > 1:
return email
else:
return '@'.join([ email, gethostname()])
msg = "%r is not a legal email address" % email
#raise OptionParser(msg)
print(msg)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description = '''Send the list of attachments as a MIME message.
The email is sent by forwarding to your local SMTP server, which then does the normal delivery process.
Your local machine must be running an SMTP server.''', add_help=False)
optgroup = parser.add_argument_group('Options')
optgroup.add_argument('-h', '--help', action='help', help="show this help message and exit")
optgroup.add_argument('-V', '--version', action='version', version='%(prog)s' + ' Version %(ver)s' % {'ver': version})
optgroup.add_argument("-d", "--debug", action="store_true", default=False, help="Emit debugging information")
arggroup = parser.add_argument_group('Arguments')
arggroup.add_argument('-f', '--sender', action='store', metavar='FROM', default=getemail('').lower(),
help='The value of the "From" header (the current value is "%s")' % getemail('').lower())
arggroup.add_argument('-t', '--recipient', action='append', metavar='TO@DEST', default=[], dest='recipients',
help='A "To" header value (one of more of these are required)')
arggroup.add_argument('-s', '--subject', action='store', metavar='SUBJECT', default='Your email attachment',
help='The value of the email\'s "Subject" header')
arggroup.add_argument('-H', '--html', action='store', metavar='HTMLBODY',
default='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html>\n<body>\n<p>Hi,</p>\n\n<p>Attached please find your files.</p>\n\n<p>Kind regards,<br>\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n\n</body>\n</html>\n\n' %
pwd.getpwnam(pwd.getpwuid(os.geteuid()).pw_name).pw_gecos.split(',')[0],
help='The body of the message with embedded control characters as required')
arggroup.add_argument('-b', '-m', '--body', action='store', metavar='BODY',
default='Hi,\n\nAttached please find your files.\n\nKind regards,\n\t%s\n\n' %
pwd.getpwnam(pwd.getpwuid(os.geteuid()).pw_name).pw_gecos.split(',')[0],
help='The body of the message with embedded control characters as required')
arggroup.add_argument('-a', '--file', default=[], dest='files', action='append', metavar='FILE',
help='A file to be attached. More than one may be used.')
arggroup.add_argument('-o', '--output', action='store', metavar='FILE',
help="""Print the composed message to FILE instead of sending the message to the SMTP server.""")
args = parser.parse_args()
if not args.sender or not args.recipients:
parser.print_help()
sys.exit(1)
if args.debug:
debugflag = True
if args.body == '-':
args.body = "\n".join(sys.stdin)
# Create the enclosing (outer) message
_recipients = []
for _x in args.recipients:
for _y in _x.split(','):
_recipients.append(_y)
args.recipients = _recipients
debug(args.sender, "From")
debug(COMMASPACE.join(args.recipients), "To")
debug(args.subject, "Subject")
outer = MIMEMultipart('mixed')
outer['Subject'] = args.subject
outer['To'] = COMMASPACE.join(args.recipients)
outer['From'] = args.sender
outer.preamble = 'For the best experience, please use a MIME-aware mail reader.\n'
try:
body = args.body.decode('ascii') + "\n\n"
utf8 = False
debug("ASCII", "Encoding")
except UnicodeDecodeError:
body = args.body.decode('utf-8', 'ignore') + u"\n\n"
utf8 = True
debug("UTF-8", "Encoding")
if utf8:
body = args.encode('utf-8')
outer.attach(MIMEText(body.decode('string_escape'), 'plain', 'utf-8'))
else:
outer.attach(MIMEText(body.decode('string_escape'), 'plain'))
for filename in args.files:
if not os.path.isfile(filename):
continue
# Guess the content type based on the file's extension. Encoding
# will be ignored, although we should check for simple things like
# gzip'd or compressed files.
ctype, encoding = mimetypes.guess_type(filename)
if ctype is None or encoding is not None:
# No guess could be made, or the file is encoded (compressed), so
# use a generic bag-of-bits type.
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
with open(filename) as fp:
if maintype == 'text':
# Note: we should handle calculating the charset
msg = MIMEText(fp.read(), _subtype=subtype)
elif maintype == 'image':
msg = MIMEImage(fp.read(), _subtype=subtype)
elif maintype == 'audio':
msg = MIMEAudio(fp.read(), _subtype=subtype)
else:
msg = MIMEBase(maintype, subtype)
msg.set_payload(fp.read())
fp.close()
# Encode the payload using Base64
encoders.encode_base64(msg)
# Set the filename parameter
msg.add_header('Content-Disposition', 'attachment', filename=os.path.split(filename)[1])
outer.attach(msg)
# Now send or store the message
composed = outer.as_string()
if args.output:
if args.output == '-':
args.output = '/dev/stdout'
fp = open(args.output, 'w')
fp.write(composed)
fp.close()
else:
s = smtplib.SMTP('localhost')
s.sendmail(args.sender, args.recipients, composed)
s.quit()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment