email-smtp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- email-smtp-1.0.0/LICENSE +21 -0
- email-smtp-1.0.0/PKG-INFO +27 -0
- email-smtp-1.0.0/README.md +10 -0
- email-smtp-1.0.0/em/__init__.py +336 -0
- email-smtp-1.0.0/em/__main__.py +1 -0
- email-smtp-1.0.0/email_smtp.egg-info/PKG-INFO +27 -0
- email-smtp-1.0.0/email_smtp.egg-info/SOURCES.txt +12 -0
- email-smtp-1.0.0/email_smtp.egg-info/dependency_links.txt +1 -0
- email-smtp-1.0.0/email_smtp.egg-info/requires.txt +3 -0
- email-smtp-1.0.0/email_smtp.egg-info/top_level.txt +1 -0
- email-smtp-1.0.0/email_smtp.egg-info/zip-safe +1 -0
- email-smtp-1.0.0/setup.cfg +7 -0
- email-smtp-1.0.0/setup.py +29 -0
email-smtp-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ouroboros Coding Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: email-smtp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: UNKNOWN
|
|
5
|
+
Home-page: https://github.com/ouroboroscoding/email-smtp-python
|
|
6
|
+
Author: Chris Nasr - Ouroboros Coding Inc.
|
|
7
|
+
Author-email: chris@ouroboroscoding.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/ouroboroscoding/email-smtp-python
|
|
10
|
+
Project-URL: Tracker, https://github.com/ouroboroscoding/email-smtp-python/issues
|
|
11
|
+
Keywords: json
|
|
12
|
+
Platform: UNKNOWN
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# Email SMTP
|
|
18
|
+
Handles sending emails using SMTP
|
|
19
|
+
|
|
20
|
+
# Install
|
|
21
|
+
```
|
|
22
|
+
pip install email-smtp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# Requires
|
|
26
|
+
email-smtp requires python 3.10 or higher
|
|
27
|
+
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# coding=utf8
|
|
2
|
+
""" Email
|
|
3
|
+
|
|
4
|
+
Wrapper for python smtp module
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__author__ = "Chris Nasr"
|
|
8
|
+
__copyright__ = "Ouroboros Coding Inc."
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__email__ = "chris@ouroboroscoding.com"
|
|
11
|
+
__created__ = "2018-11-17"
|
|
12
|
+
|
|
13
|
+
# Ouroboros imports
|
|
14
|
+
from config import config
|
|
15
|
+
from tools import evaluate
|
|
16
|
+
import undefined
|
|
17
|
+
|
|
18
|
+
# Python imports
|
|
19
|
+
from base64 import b64decode
|
|
20
|
+
from email.mime.application import MIMEApplication
|
|
21
|
+
from email.mime.multipart import MIMEMultipart
|
|
22
|
+
from email.mime.text import MIMEText
|
|
23
|
+
from email.utils import formatdate
|
|
24
|
+
from os.path import basename
|
|
25
|
+
import platform
|
|
26
|
+
import re
|
|
27
|
+
import smtplib
|
|
28
|
+
import socket
|
|
29
|
+
from sys import stderr
|
|
30
|
+
from typing import List
|
|
31
|
+
|
|
32
|
+
# Init the local variables
|
|
33
|
+
__smtp = None
|
|
34
|
+
|
|
35
|
+
# Init the last error message
|
|
36
|
+
__error: str = ''
|
|
37
|
+
|
|
38
|
+
__regex = re.compile(r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$')
|
|
39
|
+
"""E-mail address regular expression"""
|
|
40
|
+
|
|
41
|
+
# Create the defines
|
|
42
|
+
OK: int = 0
|
|
43
|
+
EUNKNOWN: int = -1
|
|
44
|
+
ECONNECT: int = -2
|
|
45
|
+
ELOGIN: int = -3
|
|
46
|
+
|
|
47
|
+
def _addresses(l):
|
|
48
|
+
"""Addresses
|
|
49
|
+
|
|
50
|
+
Takes a string or list of strings and returns them formatted for to:, cc:, \
|
|
51
|
+
or bcc:
|
|
52
|
+
|
|
53
|
+
Arguments:
|
|
54
|
+
l (str | str[]): The address or list of addresses
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
str
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# If we got a list, tuple, or set, join them, else return as is
|
|
61
|
+
if isinstance(l, (list,tuple,set)): return ', '.join(l)
|
|
62
|
+
else: return l
|
|
63
|
+
|
|
64
|
+
def _to(l):
|
|
65
|
+
"""To
|
|
66
|
+
|
|
67
|
+
Converts all addresses passed, whether strings or lists, into one singular \
|
|
68
|
+
list
|
|
69
|
+
|
|
70
|
+
Arguments:
|
|
71
|
+
l (list(str | str[])): The list of addresses or lists of addresses
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
list
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Init the return list
|
|
78
|
+
lRet = []
|
|
79
|
+
|
|
80
|
+
# Go through each item in the list
|
|
81
|
+
for m in l:
|
|
82
|
+
|
|
83
|
+
# If we got a list, extend our existing list with it
|
|
84
|
+
if isinstance(m, (list,tuple)):
|
|
85
|
+
lRet.extend(m)
|
|
86
|
+
|
|
87
|
+
# Else, we got one address, just append it to our existing list
|
|
88
|
+
else:
|
|
89
|
+
lRet.append(m)
|
|
90
|
+
|
|
91
|
+
# Return the full list
|
|
92
|
+
return lRet
|
|
93
|
+
|
|
94
|
+
def error(message, to = undefined):
|
|
95
|
+
"""Email Error
|
|
96
|
+
|
|
97
|
+
Send out an email with an error message
|
|
98
|
+
|
|
99
|
+
Arguments:
|
|
100
|
+
message (str): The error to email
|
|
101
|
+
to (str | str[]): The people to email, defaults to config.email.error_to
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
bool
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
global __error
|
|
108
|
+
|
|
109
|
+
# If there's no to
|
|
110
|
+
if to is undefined:
|
|
111
|
+
|
|
112
|
+
# Get the address or addresses to email errors to
|
|
113
|
+
to = config.email.error_to()
|
|
114
|
+
|
|
115
|
+
# If we have no error_to
|
|
116
|
+
if not to:
|
|
117
|
+
raise KeyError('error_to', 'missing from config.email')
|
|
118
|
+
|
|
119
|
+
# Send the email
|
|
120
|
+
iRes = send(to, '%s Error' % platform.node(), {
|
|
121
|
+
'text': message
|
|
122
|
+
})
|
|
123
|
+
if iRes != OK:
|
|
124
|
+
print(
|
|
125
|
+
'Failed to send email: %s (%d)' % (__error, iRes),
|
|
126
|
+
file = stderr
|
|
127
|
+
)
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
# Return OK
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
def last_error() -> str:
|
|
134
|
+
"""Last Error
|
|
135
|
+
|
|
136
|
+
Returns the last error message if there is one
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
str
|
|
140
|
+
"""
|
|
141
|
+
global __error
|
|
142
|
+
return __error
|
|
143
|
+
|
|
144
|
+
def send(to: str | List[str], subject: str, opts: dict) -> int:
|
|
145
|
+
"""Send
|
|
146
|
+
|
|
147
|
+
Sends an e-mail to one or many addresses based on a dictionary of options
|
|
148
|
+
|
|
149
|
+
Arguments:
|
|
150
|
+
to (str | str[]): The email or emails to send the email to
|
|
151
|
+
subject (str): The subject of the email
|
|
152
|
+
opts (dict): The options used to generate the email and any headers
|
|
153
|
+
'html': str
|
|
154
|
+
'text': str
|
|
155
|
+
'from': str,
|
|
156
|
+
'reply-to': str
|
|
157
|
+
'cc': str | str[]
|
|
158
|
+
'bcc': str | str[]
|
|
159
|
+
'attachments': list(str | dict('body', 'filename')) \
|
|
160
|
+
If an attachment is a string, a local filename is \
|
|
161
|
+
assumed, else if we receive a dictionary, it should \
|
|
162
|
+
contain the filename of the file, and the raw body \
|
|
163
|
+
of the file
|
|
164
|
+
'unsubscribe': str
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
int
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Import the module vars
|
|
171
|
+
global __error, __smtp
|
|
172
|
+
|
|
173
|
+
# If we have no config
|
|
174
|
+
if not __smtp:
|
|
175
|
+
__smtp = config.email.smtp({
|
|
176
|
+
'host': 'localhost',
|
|
177
|
+
'port': 25,
|
|
178
|
+
'tls': False
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
# Init the list of total "to"s
|
|
182
|
+
lTO = [to]
|
|
183
|
+
|
|
184
|
+
# If from is missing, create a generic one
|
|
185
|
+
if 'from' not in opts:
|
|
186
|
+
opts['from'] = 'noreply@%s' % socket.gethostname()
|
|
187
|
+
|
|
188
|
+
# Create a new Mime MultiPart message
|
|
189
|
+
oMMP = MIMEMultipart('mixed')
|
|
190
|
+
oMMP['From'] = opts['from']
|
|
191
|
+
oMMP['To'] = _addresses(to)
|
|
192
|
+
oMMP['Date'] = formatdate()
|
|
193
|
+
oMMP['Subject'] = subject
|
|
194
|
+
|
|
195
|
+
# If we have a reply-to
|
|
196
|
+
if 'reply-to' in opts:
|
|
197
|
+
oMMP['reply-to'] = opts['reply-to']
|
|
198
|
+
|
|
199
|
+
# If we have cc
|
|
200
|
+
if 'cc' in opts:
|
|
201
|
+
oMMP['Cc'] = _addresses(opts['cc'])
|
|
202
|
+
lTO.append(opts['cc'])
|
|
203
|
+
|
|
204
|
+
# If we have bcc
|
|
205
|
+
if 'bcc' in opts:
|
|
206
|
+
lTO.append(opts['bcc'])
|
|
207
|
+
|
|
208
|
+
# If we have an unsubscribe string
|
|
209
|
+
if 'unsubscribe' in opts:
|
|
210
|
+
oMMP.add_header('List-Unsubscribe', opts['unsubscribe'])
|
|
211
|
+
|
|
212
|
+
# Create the alternative part for the content
|
|
213
|
+
oAlternative = MIMEMultipart('alternative')
|
|
214
|
+
|
|
215
|
+
# Check that text or html body is set
|
|
216
|
+
if 'text' not in opts and 'html' not in opts:
|
|
217
|
+
raise ValueError('need one of "text" or "html" in em.send options')
|
|
218
|
+
|
|
219
|
+
# Attach the main message
|
|
220
|
+
if 'text' in opts and opts['text']:
|
|
221
|
+
oAlternative.attach(MIMEText(opts['text'], 'plain'))
|
|
222
|
+
if 'html' in opts and opts['html']:
|
|
223
|
+
oAlternative.attach(MIMEText(opts['html'], 'html'))
|
|
224
|
+
|
|
225
|
+
# Add the alternative section to the email
|
|
226
|
+
oMMP.attach(oAlternative)
|
|
227
|
+
|
|
228
|
+
# If there's any attachments
|
|
229
|
+
if 'attachments' in opts:
|
|
230
|
+
|
|
231
|
+
# Make sure it's a list
|
|
232
|
+
if not isinstance(opts['attachments'], (list,tuple)):
|
|
233
|
+
opts['attachments'] = [opts['attachments']]
|
|
234
|
+
|
|
235
|
+
# Loop through the attachments
|
|
236
|
+
for i in range(len(opts['attachments'])):
|
|
237
|
+
|
|
238
|
+
# If we got a string
|
|
239
|
+
if isinstance(opts['attachments'][i], str):
|
|
240
|
+
|
|
241
|
+
# Assume it's a file and open it
|
|
242
|
+
with open(opts['attachments'][i], 'rb') as rFile:
|
|
243
|
+
oMMP.attach(MIMEApplication(
|
|
244
|
+
rFile.read(),
|
|
245
|
+
Content_Disposition='attachment; filename="%s"' %
|
|
246
|
+
basename(opts['attachments'][i]),
|
|
247
|
+
Name=basename(opts['attachments'][i])
|
|
248
|
+
))
|
|
249
|
+
|
|
250
|
+
# Else, if got a dictionary
|
|
251
|
+
elif isinstance(opts['attachments'][i], dict):
|
|
252
|
+
|
|
253
|
+
# If the fields are missing
|
|
254
|
+
try:
|
|
255
|
+
evaluate(opts['attachments'][i], ['body', 'filename'])
|
|
256
|
+
except ValueError as e:
|
|
257
|
+
raise ValueError(
|
|
258
|
+
[('attachments[%d].%s' % (i, s), 'missing') \
|
|
259
|
+
for s in e.args]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Add it
|
|
263
|
+
oMMP.attach(MIMEApplication(
|
|
264
|
+
b64decode(opts['attachments'][i]['body']),
|
|
265
|
+
Content_Disposition='attachment; filename="%s"' %
|
|
266
|
+
opts['attachments'][i]['filename'],
|
|
267
|
+
Name=opts['attachments'][i]['filename']
|
|
268
|
+
))
|
|
269
|
+
|
|
270
|
+
# Else, error
|
|
271
|
+
else:
|
|
272
|
+
raise ValueError(
|
|
273
|
+
'attachments[%d]' % i, 'invalid type, must be str or dict'
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Generate the body
|
|
277
|
+
sBody = oMMP.as_string()
|
|
278
|
+
|
|
279
|
+
# Catch any Connect or Authenticate Errors
|
|
280
|
+
try:
|
|
281
|
+
|
|
282
|
+
# Create a new instance of the SMTP class
|
|
283
|
+
oSMTP = smtplib.SMTP(__smtp['host'], __smtp['port'])
|
|
284
|
+
|
|
285
|
+
# If we need TLS
|
|
286
|
+
if __smtp['tls']:
|
|
287
|
+
|
|
288
|
+
# Start TLS
|
|
289
|
+
oSMTP.starttls()
|
|
290
|
+
|
|
291
|
+
# If there's a username
|
|
292
|
+
if __smtp['user']:
|
|
293
|
+
|
|
294
|
+
# Log in with the given credentials
|
|
295
|
+
oSMTP.login(__smtp['user'], __smtp['passwd'])
|
|
296
|
+
|
|
297
|
+
# Try to send the message, then close the SMTP
|
|
298
|
+
oSMTP.sendmail(opts['from'], _to(lTO), sBody)
|
|
299
|
+
oSMTP.close()
|
|
300
|
+
|
|
301
|
+
# Return ok
|
|
302
|
+
return OK
|
|
303
|
+
|
|
304
|
+
# If there's a connection error
|
|
305
|
+
except smtplib.SMTPConnectError as e:
|
|
306
|
+
__error = str(e.args)
|
|
307
|
+
return ECONNECT
|
|
308
|
+
|
|
309
|
+
# If there's am authentication error
|
|
310
|
+
except smtplib.SMTPAuthenticationError as e:
|
|
311
|
+
__error = str(e.args)
|
|
312
|
+
return ELOGIN
|
|
313
|
+
|
|
314
|
+
# If there's any other error
|
|
315
|
+
except (smtplib.SMTPException, Exception) as e:
|
|
316
|
+
__error = str(e.args)
|
|
317
|
+
return EUNKNOWN
|
|
318
|
+
|
|
319
|
+
def valid(address: str) -> bool:
|
|
320
|
+
"""Valid
|
|
321
|
+
|
|
322
|
+
Returns true if the email address is valid
|
|
323
|
+
|
|
324
|
+
Arguments:
|
|
325
|
+
address (str): The e-mail address to verify
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
bool
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
# If we get a match
|
|
332
|
+
if __regex.match(address):
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
# No match
|
|
336
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
print('I compile, but I don\'t necessarily run')
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: email-smtp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: UNKNOWN
|
|
5
|
+
Home-page: https://github.com/ouroboroscoding/email-smtp-python
|
|
6
|
+
Author: Chris Nasr - Ouroboros Coding Inc.
|
|
7
|
+
Author-email: chris@ouroboroscoding.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/ouroboroscoding/email-smtp-python
|
|
10
|
+
Project-URL: Tracker, https://github.com/ouroboroscoding/email-smtp-python/issues
|
|
11
|
+
Keywords: json
|
|
12
|
+
Platform: UNKNOWN
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# Email SMTP
|
|
18
|
+
Handles sending emails using SMTP
|
|
19
|
+
|
|
20
|
+
# Install
|
|
21
|
+
```
|
|
22
|
+
pip install email-smtp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# Requires
|
|
26
|
+
email-smtp requires python 3.10 or higher
|
|
27
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.cfg
|
|
4
|
+
setup.py
|
|
5
|
+
em/__init__.py
|
|
6
|
+
em/__main__.py
|
|
7
|
+
email_smtp.egg-info/PKG-INFO
|
|
8
|
+
email_smtp.egg-info/SOURCES.txt
|
|
9
|
+
email_smtp.egg-info/dependency_links.txt
|
|
10
|
+
email_smtp.egg-info/requires.txt
|
|
11
|
+
email_smtp.egg-info/top_level.txt
|
|
12
|
+
email_smtp.egg-info/zip-safe
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
em
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
with open('README.md', 'r') as oF:
|
|
4
|
+
long_description=oF.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name='email-smtp',
|
|
8
|
+
version='1.0.0',
|
|
9
|
+
description='',
|
|
10
|
+
long_description=long_description,
|
|
11
|
+
long_description_content_type='text/markdown',
|
|
12
|
+
url='https://github.com/ouroboroscoding/email-smtp-python',
|
|
13
|
+
project_urls={
|
|
14
|
+
'Source': 'https://github.com/ouroboroscoding/email-smtp-python',
|
|
15
|
+
'Tracker': 'https://github.com/ouroboroscoding/email-smtp-python/issues'
|
|
16
|
+
},
|
|
17
|
+
keywords=['json'],
|
|
18
|
+
author='Chris Nasr - Ouroboros Coding Inc.',
|
|
19
|
+
author_email='chris@ouroboroscoding.com',
|
|
20
|
+
license='MIT',
|
|
21
|
+
packages=['em'],
|
|
22
|
+
python_requires='>=3.10',
|
|
23
|
+
install_requires=[
|
|
24
|
+
'config-oc>=1.0.3,<1.1',
|
|
25
|
+
'tools-oc>=1.2.3,<1.3',
|
|
26
|
+
'undefined-oc>=1.0.0,<1.1'
|
|
27
|
+
],
|
|
28
|
+
zip_safe=True
|
|
29
|
+
)
|