├── 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 |
--------------------------------------------------------------------------------