├── README.rst └── SendmailViaSMTP.py /README.rst: -------------------------------------------------------------------------------- 1 | README 2 | ======= 3 | 4 | .. contents:: 5 | 6 | What is SendmailViaSMTP 7 | ------------------------- 8 | 9 | SendmailViaSMTP is an easy used command line kit for sending mail via smtp server which can use in multiple platforms like linux, BSD, Windows etc. Like `msmtp `_ but don't need compile and install in each machine. Just copy SendmailViaSMTP.py to any machine which had python installed. 10 | SendmailViaSMTP is distributed under the BSD license. 11 | 12 | Where is SendmailViaSMTP 13 | -------------------------- 14 | * Homepage: http://www.himysql.com (Chinese) 15 | * Source code: https://github.com/leopku/SendmailViaSMTP 16 | 17 | Features 18 | --------- 19 | 20 | * Accept data from pipe as email body. 21 | * Tls support. This means we support sending mail using gmail. 22 | * Multi recipient support. 23 | * Multi system supported, like Linux, BSD, Windows etc. 24 | * **NEW**: Attachments support(using -a/--attach option, see help message for more). 25 | * log support. use --log option for debugging. 26 | 27 | Requirements 28 | ------------- 29 | 30 | * `python`_ 2.3.x 31 | 32 | .. _python: http://www.python.org/ 33 | 34 | How to use SendmailViaSMTP 35 | ---------------------------- 36 | 37 | Download 38 | ~~~~~~~~~ 39 | #. Git clone(recommend) 40 | $git clone https://github.com/leopku/SendmailViaSMTP.git 41 | 42 | #. Download 43 | #. Dowload last source code zip/tar.gz package from http://github.com/leopku/SendmailViaSMTP/archives/master 44 | #. Download last package uploading by developer from http://github.com/leopku/SendmailViaSMTP/downloads 45 | 46 | Run SendmailViaSMTP.py in terminal 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | #. pipe mode [#]_ 49 | $echo "contents from pipe." | python SendmailViaSMTP.py --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" 50 | 51 | #. file mode 52 | $python SendmailViaSMTP.py --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" --file="/path/to/file" 53 | 54 | #. option mode 55 | $python SendmailViaSMTP.py --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" --content="contents from option" 56 | 57 | **NOTE:** The priority of three mode: pipe mode, file mode, option mode. 58 | 59 | TODO 60 | ----- 61 | 62 | .. [#] pipe mode is very useful while working with nagios etc. 63 | -------------------------------------------------------------------------------- /SendmailViaSMTP.py: -------------------------------------------------------------------------------- 1 | #! /bin/env python 2 | ############################################## 3 | # SendmailViaSMTP is a kit for sending mail 4 | # under console throught exist SMTP server. 5 | # 6 | # Author: leopku#qq.com 7 | # 8 | # History: 9 | # 2013-04-23: 10 | # * fixed a bug while adding an attachment(thanks http://weibo.com/u/1738440993). 11 | # 2012-10-29: 12 | # * fixed problem under python 2.3.x. 13 | # 2012-10-09: 14 | # * fixed bug under crontab. 15 | # * adjust priority to --file, --content and piped mode because of the bug above. 16 | # 2012-09-30: 17 | # + add --log option for debugging errors. 18 | # 2012-09-24: 19 | # * restruct the codes. 20 | # * update the version number to 1.2. 21 | # 2012-08-20: 22 | # * fixed homepage link broken in README.rst(thanks http://weibo.com/royshan ). 23 | # 2012-08-15: 24 | # + support attachments(-a or --attach option). 25 | # 2012-03-13: 26 | # * Fixed sending mail under python 2.4x(thanks yong.yang). 27 | # 2011-12-28: 28 | # + add README.rst. 29 | # + add pipe mode -- accept data as mail body which transfered through pipe. 30 | # + implement file mode. 31 | # * adjust the file mode has higher priority than option mode. 32 | # 2011-12-23: 33 | # * Fixed auth not supported issue under 2.5.x(thanks doitmy). 34 | # 2010-09-28: 35 | # * fixed --tls as turn on/off option. 36 | # * optimize help message. 37 | # 2010-09-27: 38 | # + add support for Gmail(smtp.gmail.com). 39 | # * fixed bugs for multi recipients. 40 | # + first release. 41 | ############################################## 42 | 43 | import sys 44 | import os 45 | import os.path 46 | import fileinput 47 | 48 | import smtplib 49 | import mimetypes 50 | from email import Encoders 51 | from email.MIMEBase import MIMEBase 52 | from email.MIMEMultipart import MIMEMultipart 53 | from email.MIMEText import MIMEText 54 | from email.MIMEImage import MIMEImage 55 | 56 | __author__ ="leopku#qq.com" 57 | __date__ ="$2012-08-25 14:05:56$" 58 | 59 | __usage__ = u'''python %prog [--host=smtp.yourdomain.com] <--port=110> [--user=smtpaccount] [--password=smtppass] <--subject=subject> [--file=filename]|[--content=mailbody] [--from=sender] [--to=reciver]. 60 | 61 | example: 62 | 1. echo "blablabla" | python %prog --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" 63 | 2. python %prog --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" --file=/path/of/file 64 | 3. python %prog --host="mail.yourdomain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain2.com;friends3@domain3.com" --user="myname@yourdomain.com" --password="p4word" -s "Hello from MailViaSMTP" -c "This is a mail just for testing." 65 | 66 | The priority of three content inputing method is: piped-data, --file, --content.''' 67 | 68 | __version__ = '%prog 1.2' 69 | __desc__ = u'''This is a command line kit for sending mail via smtp server which can use in multiple platforms like linux, BSD, Windows etc. 70 | This little kit was written by %s using python. 71 | The minimum version of python required was 2.3.''' % (__author__) 72 | 73 | class Mail: 74 | """docstring for Mail""" 75 | def __init__(self, subject='', content='', m_from='', m_to='', m_cc=''): 76 | self.subject = subject 77 | self.content = MIMEText(content, 'html', 'utf-8') 78 | self.m_from = m_from 79 | self.m_to = m_to 80 | self.m_cc = m_cc 81 | 82 | self.body = MIMEMultipart('related') 83 | self.body['Subject'] = self.subject 84 | self.body['From'] = self.m_from 85 | self.body['To'] = self.m_to 86 | self.body.preamble = 'This is a multi-part message in MIME format.' 87 | 88 | self.alternative = MIMEMultipart('alternative') 89 | self.body.attach(self.alternative) 90 | self.alternative.attach(self.content) 91 | 92 | def attach(self, attachments): 93 | if attachments: 94 | for attachment in attachments: 95 | if not os.path.isfile(attachment): 96 | print 'WARNING: Unable to attach %s because it is not a file.' % attachment 97 | continue 98 | 99 | ctype, encoding = mimetypes.guess_type(attachment) 100 | if ctype is None or encoding is not None: 101 | ctype = 'application/octet-stream' 102 | maintype, subtype = ctype.split('/', 1) 103 | 104 | fp = open(attachment, 'rb') 105 | attachment_mime = MIMEBase("application", "octet-stream") 106 | attachment_mime.set_payload(fp.read()) 107 | fp.close() 108 | 109 | Encoders.encode_base64(attachment_mime) 110 | attachment_mime.add_header('Content-Disposition', 'attachment', filename=attachment) 111 | self.body.attach(attachment_mime) 112 | 113 | class SMTPServer: 114 | """docstring for SMTPServer""" 115 | def __init__(self, host='localhost', user='', password='', port=25, tls=False): 116 | self.port = port 117 | self.smtp = smtplib.SMTP() 118 | self.host = host 119 | self.user = user 120 | self.password = password 121 | self.is_gmail = False 122 | if self.host == 'smtp.gmail.com': 123 | self.is_gmail = True 124 | self.port = 587 125 | self.tls = tls 126 | 127 | def sendmail(self, mail): 128 | self.smtp.connect(self.host, self.port) 129 | if self.tls or self.is_gmail: 130 | self.smtp.starttls() 131 | self.smtp.ehlo() 132 | self.smtp.esmtp_features['auth'] = 'LOGIN DIGEST-MD5 PLAIN' 133 | if self.user: 134 | self.smtp.login(self.user, self.password) 135 | self.smtp.sendmail(mail.m_from, mail.m_to.split(';'), mail.body.as_string()) 136 | self.smtp.quit() 137 | 138 | if __name__ == "__main__": 139 | import optparse 140 | import logging 141 | 142 | PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) 143 | LOG_FILENAME = os.path.join(PROJECT_ROOT, 'sendmail.log') 144 | 145 | parser = optparse.OptionParser(usage=__usage__, version=__version__, description=__desc__) 146 | parser.add_option('-a', '--attach', dest='attach', default=[], action='append', help='Specifies a file as attachment to be attached. Can be specified more than once.') 147 | parser.add_option('-s', '--subject', dest='subject', help='The subject of the mail.') 148 | parser.add_option('-c', '--content', dest='content', help='option mode. Mail body should be passed through this option. Note: this option should be ignored while working with piped-data or --file option.') 149 | parser.add_option('-f', '--from', dest='address_from', metavar='my@domain.com', help='Set envelope from address. If --user option is not empty, commonly this option should be equaled with --user options. Otherwize, the authoration of the smtp server should be failed.') 150 | parser.add_option('-t', '--to', dest='address_to', metavar='friend@domain2.com', help='Set recipient address. Use semicolon to seperate multi recipient, for example: "a@a.com;b@b.com."') 151 | parser.add_option('-F', '--file', dest='file', help='File mode. Read mail body from file. NOTE: this option should be ignored while working with piped-data.') 152 | parser.add_option('--host', dest='host', metavar='smtp.domain.com', help='SMTP server host name or ip. Like "smtp.gmail.com" through GMail(tm) or "192.168.0.99" through your own smtp server.') 153 | parser.add_option('-P', '--port', dest='port', type='int', default=25, help='SMTP server port number. Default is %default.') 154 | parser.add_option('-u', '--user', dest='user', metavar='my@domain.com', help='The username for SMTP server authorcation. Left this option empty for non-auth smtp server.') 155 | parser.add_option('-p', '--password', dest='password', help='The password for SMTP server authorcation. NOTE: if --user option is empty, this option will be ignored.') 156 | parser.add_option('--tls', dest='tls', action='store_true', help='Using tls to communicate with SMTP server. Default is false. NOTE: if --host option equals "smtp.gmail.com", this option becomes defaults true.') 157 | parser.add_option('--log', dest='log', default='critical', help='specify --log=DEBUG or --log=debug, more info see document for logging module.') 158 | opts, args= parser.parse_args() 159 | 160 | numeric_level = getattr(logging, opts.log.upper(), None) 161 | if not isinstance(numeric_level, int): 162 | raise ValueError('Invalid log level: %s' % opts.log) 163 | if sys.version_info < (2, 3, 0): 164 | raise 'Python runtime MUST greater than 2.3.0' 165 | elif sys.version_info > (3, 0, 0): 166 | raise 'Python 3.0 was NOT recommented!' 167 | elif sys.version_info >= (2, 3, 0) and sys.version_info < (2, 4, 0): 168 | logging.basicConfig() 169 | else: 170 | logging.basicConfig(filename=LOG_FILENAME, level=numeric_level, format='%(asctime)s %(message)s') 171 | 172 | if opts.host is None or opts.address_from is None or opts.address_to is None: 173 | msg = '''ERROR: All parameters followed were required: --host, --from and --to. 174 | Use -h to get more help.''' 175 | logging.critical(msg) 176 | sys.exit(msg) 177 | 178 | content = None 179 | filename = None 180 | if opts.content: 181 | logging.debug('[param mode] %s' % opts.content) 182 | content = opts.content # content mode, mail content should read from --content option. 183 | 184 | if opts.file: 185 | logging.debug('[file mode] %s' % opts.file) 186 | filename = opts.file # file mode, mail content should read from file. 187 | if content is None and filename is None and not isatty(0): 188 | logging.debug('[pip mode]') 189 | filename = '-' # pipe mode - mail content should read from stdin. 190 | if filename: 191 | try: 192 | fi = fileinput.FileInput(filename) 193 | logging.debug('[filename] %s' % filename) 194 | content = '
'.join(fi) 195 | except: 196 | logging.critical('can not open %s.' % filename) 197 | logging.debug('[content]%s' % content) 198 | if content: 199 | try: 200 | logging.info('preparing mail...') 201 | mail = Mail(opts.subject, content, opts.address_from, opts.address_to) 202 | logging.info('preparing attachments...') 203 | mail.attach(opts.attach) 204 | logging.info('preparing SMTP server...') 205 | smtp = SMTPServer(opts.host, opts.user, opts.password, opts.port, opts.tls) 206 | logging.info('sending mail...') 207 | smtp.sendmail(mail) 208 | logging.info('all done.') 209 | except Exception, e: 210 | logging.critical('[Exception]%s' % e) 211 | else: 212 | msg = '''ERROR: Mail content is EMPTY! Please specify one option of listed: piped-data, --file or --content. 213 | Use -h to get more help.''' 214 | logging.critical(msg) 215 | sys.exit(msg) 216 | --------------------------------------------------------------------------------