-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemail_tools.py
More file actions
326 lines (262 loc) · 9.39 KB
/
email_tools.py
File metadata and controls
326 lines (262 loc) · 9.39 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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
import hmac
import hashlib
import ssl
import smtplib
import random
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.message import EmailMessage
from flask_babel import _
from settings import (
EMAIL_SENDER,
SMTP_PORT,
SMTP_SERVER,
SMTP_TEST,
SMTP_USERNAME,
SMTP_PASSWORD,
SMTP_USE_STARTTLS,
SMTP_USE_SSL,
SMTP_TIMEOUT_SECONDS,
APP_SEC_KEY,
APP_DOMAIN,
)
from urllib.parse import quote
def _deliver_message(message):
"""Deliver an email using secure SMTP settings from environment.
Behavior:
- Test mode prints message and skips network delivery.
- Supports implicit TLS (SMTP_SSL) and STARTTLS.
- Supports authenticated SMTP when username/password are provided.
Args:
message: EmailMessage instance ready to send.
Returns:
None.
"""
if SMTP_TEST:
print(message)
return
has_user = bool(SMTP_USERNAME)
has_pass = bool(SMTP_PASSWORD)
if has_user != has_pass:
raise ValueError("SMTP auth requires both SMTP_USERNAME and SMTP_PASSWORD.")
tls_context = ssl.create_default_context()
if SMTP_USE_SSL:
with smtplib.SMTP_SSL(
SMTP_SERVER,
SMTP_PORT,
timeout=SMTP_TIMEOUT_SECONDS,
context=tls_context,
) as server:
server.ehlo()
if has_user:
server.login(SMTP_USERNAME, SMTP_PASSWORD)
server.send_message(message)
return
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout=SMTP_TIMEOUT_SECONDS) as server:
server.ehlo()
if SMTP_USE_STARTTLS:
server.starttls(context=tls_context)
server.ehlo()
if has_user:
server.login(SMTP_USERNAME, SMTP_PASSWORD)
server.send_message(message)
def send_device_added(to_email, public_key, device_type='Water Level S1 Sensor'):
"""Send a notification email when a device gets linked to a user account.
Args:
to_email: Recipient email address.
public_key: Device public key used to build monitor URL.
device_type: Human-readable device model name.
Returns:
None.
"""
confirmation_code = generate_confirmation_code(to_email)
encoded_email = quote(to_email)
# Create the message
message = EmailMessage()
message["Subject"] = f"{device_type} linked to your account!"
message["From"] = EMAIL_SENDER
message["To"] = to_email
alert_body_text = f""" <h2>{device_type} successfully linked to your account!</h2>
<p>
Here can monitor your device:
<a href='{APP_DOMAIN}/device_info?public_key={public_key}' target='_blank' >Device Monitor Link<a/>
<br>
<a href='{APP_DOMAIN}' target='_blank' >Login<a/> to your account for adjust device settings.
</p>
"""
alert_body_html = f""" <h2>{device_type} successfully linked to your account!</h2>
<p>
Here can monitor your device:
<a href='{APP_DOMAIN}/device_info?public_key={public_key}' target='_blank' >Device Monitor Link<a/>
<br>
<a href='{APP_DOMAIN}' target='_blank' >Login<a/> to your account for adjust device settings.
</p>
"""
# Create a plain text alternative for email clients that don't support HTML
message.set_content(f"""\
{alert_body_text}
WaterLevel.Pro Services
""")
# Create the HTML content
message.add_alternative(f"""\
<html>
<body>
{alert_body_html}
<p>
Best Regards,<br>
<a href='{APP_DOMAIN}' target='_blank' >
WaterLevel.Pro Services
<a/>
</p>
</body>
</html>
""", subtype='html')
_deliver_message(message)
def send_register_email(to_email, lang='en'):
"""Send account confirmation email with signed confirmation link.
Args:
to_email: Recipient email address.
lang: UI language hint used by translated email strings.
Returns:
None.
"""
confirmation_code = generate_confirmation_code(to_email)
encoded_email = quote(to_email)
# Create the message
message = EmailMessage()
message["Subject"] = _("Register Confirm [WaterLevel.Pro]")
message["From"] = EMAIL_SENDER
message["To"] = to_email
# Marcar todas las cadenas traducibles por separado
welcome_msg = _("Welcome to WaterLevel.Pro Services!")
open_link_msg = _("Open this link")
confirm_msg = _("to confirm your WaterLevel.Pro account.")
regards_msg = _("Best Regards,")
# Create a plain text alternative for email clients that don't support HTML
message.set_content(f"""
{welcome_msg}
{open_link_msg} {APP_DOMAIN}/user-confirm?code={confirmation_code}&email={encoded_email} {confirm_msg}
{regards_msg}
WaterLevel.Pro
""")
# Create the HTML content
message.add_alternative(f"""\
<html>
<body>
<h4>{welcome_msg}</h4>
<p>
<a href='{APP_DOMAIN}/user-confirm?code={confirmation_code}&email={encoded_email}' target='_blank' >
{open_link_msg} </a> {confirm_msg}
</p>
<p>
{regards_msg}<br>
<a href='{APP_DOMAIN}/user-confirm?code={confirmation_code}&email={encoded_email}' target='_blank' >
WaterLevel.Pro
<a/>
</p>
</body>
</html>
""", subtype='html')
_deliver_message(message)
def send_alert_email(to_email, alert_subject, alert_body):
"""Send a level/offline alert email to a user.
Args:
to_email: Recipient email address.
alert_subject: Subject line for the email.
alert_body: HTML/text content fragment for alert details.
Returns:
None.
"""
confirmation_code = generate_confirmation_code(to_email)
encoded_email = quote(to_email)
# Create the message
message = EmailMessage()
message["Subject"] = alert_subject
message["From"] = EMAIL_SENDER
message["To"] = to_email
# Create a plain text alternative for email clients that don't support HTML
message.set_content(f"""\
{alert_body}
WaterLevel.Pro Services
""")
# Create the HTML content
message.add_alternative(f"""\
<html>
<body>
{alert_body}
<p>
<a href='{APP_DOMAIN}' target='_blank' >Login<a/> to your account for adjust email alert settings.
</p>
<p>
Best Regards,<br>
<a href='{APP_DOMAIN}' target='_blank' >
WaterLevel.Pro Services
<a/>
</p>
</body>
</html>
""", subtype='html')
_deliver_message(message)
def generate_confirmation_code(to_email):
"""Generate deterministic HMAC confirmation token for an email address.
Args:
to_email: Email to sign with application secret.
Returns:
str: Hex-encoded HMAC-SHA256 token.
"""
to_email = to_email.encode()
return hmac.new(APP_SEC_KEY.encode(), to_email, hashlib.sha256).hexdigest()
def check_confirmation_code(to_email, hmac_digest):
"""Validate confirmation token generated for an email address.
Args:
to_email: Email address associated with the token.
hmac_digest: Candidate token received from user link.
Returns:
bool: True when token matches computed HMAC.
"""
to_email = to_email.encode()
hmac_digest = hmac_digest.encode()
hmac_sha256_verifier = hmac.new(APP_SEC_KEY.encode(), to_email, hashlib.sha256)
if hmac.compare_digest(hmac_digest, hmac_sha256_verifier.hexdigest().encode()):
return True
return False
def support_email(to_email, message_reply):
"""Send a support team reply email to a customer.
Args:
to_email: Recipient customer email.
message_reply: Support response content (HTML-safe expected).
Returns:
None.
"""
confirmation_code = generate_confirmation_code(to_email)
encoded_email = quote(to_email)
# Create the message
message = EmailMessage()
message["Subject"] = "Support [WaterLevel.Pro]"
message["From"] = '"WaterLevel.Pro Support" <support@waterlevel.pro>'
message["To"] = to_email
# Create a plain text alternative for email clients that don't support HTML
message.set_content(f"""\
WaterLevel.Pro Support Reply:
{message_reply}
Best Regards,
WaterLevel.Pro Support
""")
# Create the HTML content
message.add_alternative(f"""\
<html>
<body>
<h4>WaterLevel.Pro Support Reply:</h4>
{message_reply}
<div>
Best Regards,<br>
<a href='{APP_DOMAIN}/contact' target='_blank' >
WaterLevel.Pro
<a/>
</div>
</body>
</html>
""", subtype='html')
_deliver_message(message)
if __name__ == "__main__":
send_device_added("user@example.com", "PUBLIC_KEY_EXAMPLE")