Skip to content

Commit 73b0831

Browse files
committed
Replumbed how SMTP Exceptions are handled, which involved refactoring several mail functions
- Legacy-Id: 7635
1 parent f0fcc50 commit 73b0831

3 files changed

Lines changed: 84 additions & 31 deletions

File tree

ietf/utils/mail.py

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from email import message_from_string
88
import smtplib
99
from django.conf import settings
10+
from django.contrib import messages
1011
from django.core.exceptions import ImproperlyConfigured
1112
from django.template.loader import render_to_string
1213
from django.template import Context,RequestContext
@@ -169,17 +170,19 @@ def send_mail(request, to, frm, subject, template, context, *args, **kwargs):
169170
txt = render_to_string(template, context, context_instance=mail_context(request))
170171
return send_mail_text(request, to, frm, subject, txt, *args, **kwargs)
171172

172-
173-
def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None):
174-
"""Send plain text message."""
173+
def encode_message(txt):
175174
if isinstance(txt, unicode):
176175
msg = MIMEText(txt.encode('utf-8'), 'plain', 'UTF-8')
177176
else:
178177
msg = MIMEText(txt)
178+
return msg
179+
180+
def send_mail_text(request, to, frm, subject, txt, cc=None, extra=None, toUser=False, bcc=None):
181+
"""Send plain text message."""
182+
msg = encode_message(txt)
179183
send_mail_mime(request, to, frm, subject, msg, cc, extra, toUser, bcc)
180184

181-
def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None):
182-
"""Send MIME message with content already filled in."""
185+
def condition_message(to, frm, subject, msg, cc, extra):
183186
if isinstance(frm, tuple):
184187
frm = formataddr(frm)
185188
if isinstance(to, list) or isinstance(to, tuple):
@@ -200,13 +203,21 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
200203
for k, v in extra.items():
201204
if v:
202205
msg[k] = v
206+
207+
def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=False, bcc=None):
208+
"""Send MIME message with content already filled in."""
209+
210+
condition_message(to, frm, subject, msg, cc, extra)
211+
203212
# start debug server with python -m smtpd -n -c DebuggingServer localhost:2025
204213
# then put USING_DEBUG_EMAIL_SERVER=True and EMAIL_HOST='localhost'
205214
# and EMAIL_PORT=2025 in settings_local.py
206215
debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025
207216

208217
if test_mode or debugging or settings.SERVER_MODE == 'production':
209-
send_smtp(msg, bcc)
218+
with smtp_error_logging(send_smtp) as logging_send:
219+
with smtp_error_user_warning(logging_send,request) as send:
220+
send(msg, bcc)
210221
elif settings.SERVER_MODE == 'test':
211222
if toUser:
212223
copy_email(msg, to, toUser=True, originalBcc=bcc)
@@ -219,12 +230,12 @@ def send_mail_mime(request, to, frm, subject, msg, cc=None, extra=None, toUser=F
219230
if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying
220231
if bcc:
221232
msg['X-Tracker-Bcc']=bcc
222-
copy_email(msg, copy_to,originalBcc=bcc)
233+
with smtp_error_logging(copy_email) as logging_copy:
234+
with smtp_error_user_warning(logging_copy,request) as copy:
235+
copy(msg, copy_to,originalBcc=bcc)
223236

224-
def send_mail_preformatted(request, preformatted, extra={}, override={}):
225-
"""Parse preformatted string containing mail with From:, To:, ...,
226-
and send it through the standard IETF mail interface (inserting
227-
extra headers as needed)."""
237+
def parse_preformatted(preformatted, extra={}, override={}):
238+
"""Parse preformatted string containing mail with From:, To:, ...,"""
228239
msg = message_from_string(preformatted.encode("utf-8"))
229240

230241
for k, v in override.iteritems():
@@ -242,7 +253,15 @@ def send_mail_preformatted(request, preformatted, extra={}, override={}):
242253

243254
bcc = msg['Bcc']
244255
del msg['Bcc']
245-
256+
257+
return (msg, headers, bcc)
258+
259+
def send_mail_preformatted(request, preformatted, extra={}, override={}):
260+
"""Parse preformatted string containing mail with From:, To:, ...,
261+
and send it through the standard IETF mail interface (inserting
262+
extra headers as needed)."""
263+
264+
(msg,headers,bcc) = parse_preformatted(preformatted, extra, override)
246265
send_mail_text(request, msg['To'], msg["From"], msg["Subject"], msg.get_payload(), extra=headers, bcc=bcc)
247266
return msg
248267

@@ -278,6 +297,26 @@ def log_smtp_exception(e):
278297
log(" Traceback: %s" % tb)
279298
return (extype, value, tb)
280299

300+
@contextmanager
301+
def smtp_error_user_warning(thing,request):
302+
try:
303+
yield thing
304+
except smtplib.SMTPException as e:
305+
(extype, value, tb) = log_smtp_exception(e)
306+
307+
warning = "An error occured while sending email with\n"
308+
warning += "Subject: %s\n" % e.original_msg.get('Subject','[no subject]')
309+
warning += "To: %s\n" % e.original_msg.get('To','[no to]')
310+
warning += "Cc: %s\n" % e.original_msg.get('Cc','[no cc]')
311+
if isinstance(e,SMTPSomeRefusedRecipients):
312+
warning += e.detailed_refusals()
313+
else:
314+
warning += "SMTP Exception: %s\n"%extype
315+
warning += "Error Message: %s\n\n"%value
316+
warning += "The message was not delivered to anyone."
317+
messages.warning(request,warning,extra_tags='preformatted',fail_silently=True)
318+
raise
319+
281320
@contextmanager
282321
def smtp_error_logging(thing):
283322
try:
@@ -324,9 +363,31 @@ def smtp_error_logging(thing):
324363
%s
325364
--------- END ORIGINAL MESSAGE ---------
326365
""") % e.original_msg.as_string()
366+
367+
send_error_to_secretariat(msg)
368+
369+
def send_error_to_secretariat(raw_msg):
370+
371+
(parsed_msg , headers , bcc) = parse_preformatted(raw_msg)
372+
send_msg = encode_message(parsed_msg.get_payload())
373+
cc = None
374+
condition_message(parsed_msg['To'], parsed_msg['From'], parsed_msg['Subject'], send_msg, cc, headers)
375+
376+
debugging = getattr(settings, "USING_DEBUG_EMAIL_SERVER", False) and settings.EMAIL_HOST == 'localhost' and settings.EMAIL_PORT == 2025
377+
378+
try:
379+
if test_mode or debugging or settings.SERVER_MODE == 'production':
380+
send_smtp(send_msg, bcc)
327381
try:
328-
send_mail_preformatted(request=None, preformatted=msg)
329-
except smtplib.SMTPException:
330-
log("Exception encountered while sending a ticket to the secretariat")
331-
(extype,value) = sys.exc_info()[:2]
332-
log("SMTP Exception: %s : %s" % (extype,value))
382+
copy_to = settings.EMAIL_COPY_TO
383+
except AttributeError:
384+
copy_to = "ietf.tracker.archive+%s@gmail.com" % settings.SERVER_MODE
385+
if copy_to and not test_mode and not debugging: # if we're running automated tests, this copy is just annoying
386+
if bcc:
387+
send_msg['X-Tracker-Bcc']=bcc
388+
copy_email(send_msg, copy_to,originalBcc=bcc)
389+
except smtplib.SMTPException:
390+
log("Exception encountered while sending a ticket to the secretariat")
391+
(extype,value) = sys.exc_info()[:2]
392+
log("SMTP Exception: %s : %s" % (extype,value))
393+

ietf/utils/tests.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import os.path
2-
from smtplib import SMTPRecipientsRefused
32

43
from django.conf import settings
54
from django.test import TestCase
65

76
from ietf.utils.management.commands import pyflakes
8-
from ietf.utils.mail import send_mail_text, outbox, SMTPSomeRefusedRecipients, smtp_error_logging
7+
from ietf.utils.mail import send_mail_text, outbox
98

109
class PyFlakesTestCase(TestCase):
1110

@@ -22,21 +21,13 @@ def test_address_rejected(self):
2221
def send_mail(to):
2322
send_mail_text(None, to=to, frm=None, subject="Test for rejection", txt="dummy body")
2423

25-
with self.assertRaises(SMTPSomeRefusedRecipients):
26-
send_mail('good@example.com,poison@example.com')
27-
28-
with self.assertRaises(SMTPRecipientsRefused):
29-
send_mail('poison@example.com')
30-
3124
len_before = len(outbox)
32-
with smtp_error_logging(send_mail) as send:
33-
send('good@example.com,poison@example.com')
25+
send_mail('good@example.com,poison@example.com')
3426
self.assertEqual(len(outbox),len_before+2)
3527
self.assertTrue('Some recipients were refused' in outbox[-1]['Subject'])
3628

3729
len_before = len(outbox)
38-
with smtp_error_logging(send_mail) as send:
39-
send('poison@example.com')
30+
send_mail('poison@example.com')
4031
self.assertEqual(len(outbox),len_before+2)
4132
self.assertTrue('error while sending email' in outbox[-1]['Subject'])
42-
33+

static/css/base2.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ li.info { margin: 0.5em; background-color: #ff8; }
228228
li.success { margin: 0.5em; background-color: #4f4; }
229229
li.warning { margin: 0.5em; background-color: #fc8; color: black;}
230230
li.error { margin: 0.5em; background-color: #f44; }
231+
li.preformatted { white-space:pre-wrap; }
231232

232233
.errorlist, errorlist li {
233234
background: red;
@@ -423,4 +424,4 @@ span.fieldRequired {
423424

424425
.login {
425426
font-style: italic;
426-
}
427+
}

0 commit comments

Comments
 (0)