Search code examples
pythonemailosx-snow-leopard

Using Python to send email, sourcing the recipient list from a text file


I have been able to scrape together the below code, which I can now use to send an email via OSX10.6. What I wish to add to this script however is the ability to 'source' the recipient list directly from a text file within the same directory as the python file itself.

I also wish to hard code the 'sender' element because that will remain static. Help please.

#!/usr/bin/env python

"""Send the contents of a directory as a MIME message."""

import os
import sys
import smtplib
# For guessing MIME type based on file name extension
import mimetypes

from optparse import OptionParser

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 = ', '

def main():
parser = OptionParser(usage="""\
Send the contents of a directory as a MIME message.

Usage: %prog [options]

Unless the -o option is given, 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.
""")
parser.add_option('-d','--directory',
type='string', action='store')
parser.add_option('-o', '--output',
type='string', action='store', metavar='FILE',
help="""Print the composed message to FILE instead of
sending the message to the SMTP server.""")
parser.add_option('-s', '--sender',
type='string', action='store', metavar='SENDER',
help='The value of the From: header (required)')
parser.add_option('-r', '--recipients',
type='string', action='store', metavar='RECIPIENT',
default="recipient.txt", dest='recipients',
help='A file containing email addresses of recipients. One recipient per line')
opts, args = parser.parse_args()
if not opts.sender or not opts.recipients:
    parser.print_help()
    sys.exit(1)
directory = opts.directory
if not directory:
    directory = '.'
# Create the enclosing (outer) message
outer = MIMEMultipart()
outer['Subject'] = 'Malware submission'

# Variable to hold the recipients string
rec = ""
# Open recipients file
f = open(opts.recipients)
# Loop through each line of the file
for line in f:
    # Remove whitespace from line and add COMMASPACE
    rec += line.rstrip() + COMMASPACE
f.close()

outer['To'] = rec[:-2]; #Remove last COMMASPACE
outer['From'] = opts.sender
outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'

for filename in os.listdir(directory):
    path = os.path.join(directory, filename)
    if not os.path.isfile(path):
        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(path)
    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)
    if maintype == 'text':
        fp = open(path)
    # Note: we should handle calculating the charset
        msg = MIMEText(fp.read(), _subtype=subtype)
        fp.close()
    elif maintype == 'image':
        fp = open(path, 'rb')
        msg = MIMEImage(fp.read(), _subtype=subtype)
        fp.close()
    elif maintype == 'audio':
        fp = open(path, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=subtype)
        fp.close()
    else:
        fp = open(path, 'rb')
        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=filename)
outer.attach(msg)
# Now send or store the message
composed = outer.as_string()
if opts.output:
    fp = open(opts.output, 'w')
    fp.write(composed)
    fp.close()
else:
    s = smtplib.SMTP('localhost')
    s.sendmail(opts.sender, opts.recipients, composed)
    s.quit()

if __name__ == '__main__':
main()

Many thanks.


Solution

  • OK, so I have (or more accurately a colleague has) solved the issue. Code as follows and a BIG thank you to Paul Littlefair for the solution.

    #!/usr/bin/env python
    
    """Send the contents of a directory as a MIME message."""
    
    import os
    import sys
    import smtplib
    # For guessing MIME type based on file name extension
    import mimetypes
    
    from optparse import OptionParser
    
    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 = ', '
    
    
    def main():
    parser = OptionParser(usage="""\
    Send the contents of a directory as a MIME message.
    
    Usage: %prog [options]
    
    Unless the -o option is given, 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.
    """)
    parser.add_option('-d','--directory',
    type='string', action='store')
    parser.add_option('-o', '--output',
    type='string', action='store', metavar='FILE',
    help="""Print the composed message to FILE instead of
    sending the message to the SMTP server.""")
    parser.add_option('-s', '--sender',
    type='string', action='store', metavar='SENDER',
    help='The value of the From: header (required)')
    parser.add_option('-r', '--recipient',
    type='string', action='append', metavar='RECIPIENT',
    default=[], dest='recipients'),
    parser.add_option('-f', '--recipientfile',
    type='string', action='store', metavar='RECIPIENT_FILE',
    dest='recipient_file', default="",
    help='A To: header value (a file containing this)')
    
    opts, args = parser.parse_args()
    if not opts.sender or not (opts.recipient_file or opts.recipients):
        parser.print_help()
        sys.exit(1)
    directory = opts.directory
    if not directory:
        directory = '.'
    # Create the enclosing (outer) message
    
    try:
        rec_file = open(opts.recipient_file)
        recipients = rec_file.read()
        rec_file.close()
    except IOError:
        print "/!\ Bad file. Falling back to recipent option"
        recipients = COMMASPACE.join(opts.recipients)
    
    outer = MIMEMultipart()
    outer['Subject'] = 'Malware submission'
    outer['To'] = recipients
    outer['From'] = opts.sender
    outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'
    
    for filename in os.listdir(directory):
        path = os.path.join(directory, filename)
        if not os.path.isfile(path):
            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(path)
        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)
        if maintype == 'text':
            fp = open(path)
        # Note: we should handle calculating the charset
            msg = MIMEText(fp.read(), _subtype=subtype)
            fp.close()
        elif maintype == 'image':
            fp = open(path, 'rb')
            msg = MIMEImage(fp.read(), _subtype=subtype)
            fp.close()
        elif maintype == 'audio':
            fp = open(path, 'rb')
            msg = MIMEAudio(fp.read(), _subtype=subtype)
            fp.close()
        else:
            fp = open(path, 'rb')
            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=filename)
    outer.attach(msg)
    # Now send or store the message
    composed = outer.as_string()
    if opts.output:
        fp = open(opts.output, 'w')
        fp.write(composed)
        fp.close()
    else:
        s = smtplib.SMTP('localhost')
        s.sendmail(opts.sender, recipients, composed)
        s.quit()
    
    if __name__ == '__main__':
    main()
    

    Input format is -d [dir] -s [fromaddress] -f [recipientsourcefile]