-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpython-zerosmtp.py
More file actions
90 lines (83 loc) · 3.1 KB
/
Copy pathpython-zerosmtp.py
File metadata and controls
90 lines (83 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/env python3
"""
python-zerosmtp.py
Python 3.13+ smtplib.SMTP_SSL - ZeroSMTP mx.msgwing.com:465 SSL/TLS
Production-ready | Let's Encrypt | match/case error handling
"""
import os
import ssl
from email.message import EmailMessage
from smtplib import SMTP_SSL, SMTPException, SMTPAuthenticationError
from contextlib import contextmanager
from typing import Generator
@contextmanager
def zerosmtp_connection(
username: str,
password: str,
) -> Generator[SMTP_SSL, None, None]:
"""Context manager for ZeroSMTP SMTP_SSL connection."""
context = ssl.create_default_context()
smtp = SMTP_SSL('mx.msgwing.com', 465, context=context, timeout=10)
try:
smtp.login(username, password)
yield smtp
finally:
smtp.quit()
def send_email_via_zerosmtp(
username: str,
password: str,
from_addr: str,
to_addr: str,
subject: str,
) -> bool:
"""Send HTML+plain email via ZeroSMTP."""
try:
with zerosmtp_connection(username, password) as smtp:
message = EmailMessage()
message['Subject'] = subject
message['From'] = from_addr
message['To'] = to_addr
message.set_content('Hello from ZeroSMTP! This is plain text.')
message.add_alternative(
'<html><body><h1>Hello from ZeroSMTP!</h1>'
'<p>This is an HTML email sent via mx.msgwing.com:465</p></body></html>',
subtype='html',
)
smtp.send_message(message)
return True
except SMTPAuthenticationError as e:
print(f'Authentication failed: {e}', flush=True)
return False
except SMTPException as e:
match type(e).__name__:
case 'SMTPServerDisconnected':
print(f'Server disconnected: {e}', flush=True)
case 'SMTPNotSupportedError':
print(f'SMTP feature not supported: {e}', flush=True)
case _:
print(f'SMTP error: {e}', flush=True)
return False
except Exception as e:
print(f'Unexpected error: {type(e).__name__}: {e}', flush=True)
return False
if __name__ == '__main__':
# NOTE: variable names are prefixed with ZEROSMTP_ to avoid colliding with
# reserved/OS-level variables (e.g. USERNAME is auto-set on Windows).
# Fail-fast: missing env vars raise SystemExit instead of silently using
# placeholder credentials that could leak into production.
required_vars = ['ZEROSMTP_USERNAME', 'ZEROSMTP_PASSWORD',
'ZEROSMTP_FROM', 'ZEROSMTP_TO']
missing = [v for v in required_vars if not os.getenv(v)]
if missing:
raise SystemExit(
f'ERROR: missing required environment variables: {", ".join(missing)}'
)
config = {
'username': os.environ['ZEROSMTP_USERNAME'],
'password': os.environ['ZEROSMTP_PASSWORD'],
'from_addr': os.environ['ZEROSMTP_FROM'],
'to_addr': os.environ['ZEROSMTP_TO'],
'subject': os.getenv('ZEROSMTP_SUBJECT', 'Test Email from ZeroSMTP'),
}
success = send_email_via_zerosmtp(**config)
exit(0 if success else 1)