Permalink
Browse files

experimental error logging and blacklisting

  • Loading branch information...
1 parent 6012747 commit 8eeb8794a26c7ea45affe4d2c3b067b0e58bba3a @arneb arneb committed Feb 20, 2009
Showing with 135 additions and 17 deletions.
  1. +5 −4 campaign/admin.py
  2. +32 −13 campaign/models.py
  3. +98 −0 campaign/queue.py
View
@@ -10,11 +10,12 @@
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
-from campaign.models import MailTemplate, Subscriber, Campaign, Blacklisted
+from campaign.models import MailTemplate, Subscriber, Campaign, BlacklistEntry, BounceEntry
admin.site.register(MailTemplate)
admin.site.register(Subscriber)
-admin.site.register(Blacklisted)
+admin.site.register(BlacklistEntry)
+admin.site.register(BounceEntry)
class CampaignAdmin(admin.ModelAdmin):
filter_horizontal=('recipients',)
@@ -54,8 +55,8 @@ def send_view(self, request, object_id, extra_context=None):
if not request.POST.get('send', None) == '1':
raise PermissionDenied
- obj.send()
- request.user.message_set.create(message=_(u'The %(name)s "%(obj)s" was successfully sent.' % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}))
+ num_sent = obj.send()
+ request.user.message_set.create(message=_(u'The %(name)s "%(obj)s" was successfully sent. %(num_sent)s messages delivered.' % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj), 'num_sent': num_sent,}))
return HttpResponseRedirect('../')
View
@@ -4,7 +4,7 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.core.mail import EmailMessage, SMTPConnection, EmailMultiAlternatives
-
+from campaign.queue import SMTPQueue, SMTPLoggingConnection
class MailTemplate(models.Model):
"""
@@ -77,32 +77,51 @@ class Campaign(models.Model):
def __unicode__(self):
return self.name
+
def send(self):
"""
Sends the mails to the recipients.
"""
- connection = SMTPConnection()
+ connection = SMTPLoggingConnection()
+ num_sent = self._send(connection)
+ self.sent = True
+ self.save()
+ return num_sent
+
+ def _send(self, connection):
+ """
+ Does the actual work
+ """
subject = self.template.subject
text_template = template.Template(self.template.plain)
html_template = template.Template(self.template.html)
+ sent = 0
for recipient in self.recipients.all():
- msg = EmailMultiAlternatives(subject, connection=connection, to=[recipient.email,])
- msg.body = text_template.render(template.Context({'salutation': recipient.salutation,}))
- html_content = html_template.render(template.Context({'salutation': recipient.salutation,}))
- msg.attach_alternative(html_content, 'text/html')
- msg.send()
- #print "sent one message to %s" % recipient
-
- self.sent = True
- self.save()
- #print "finished"
+ # never send mail to blacklisted email addresses
+ if not BlacklistEntry.objects.filter(email=recipient.email).count():
+ msg = EmailMultiAlternatives(subject, connection=connection, to=[recipient.email,])
+ msg.body = text_template.render(template.Context({'salutation': recipient.salutation,}))
+ html_content = html_template.render(template.Context({'salutation': recipient.salutation,}))
+ msg.attach_alternative(html_content, 'text/html')
+ sent += msg.send()
+ return sent
+
-class Blacklisted(Recipient):
+
+class BlacklistEntry(Recipient):
"""
If a user has requested removal from the subscriber-list, he is added
to the blacklist to prevent accidential adding of the same user again.
"""
+
+
+
+class BounceEntry(Recipient):
+ """
+ Records bouncing Recipients. To be processed by a human.
+ """
+ exception = models.TextField(_(u"exception"), blank=True, null=True)
View
@@ -0,0 +1,98 @@
+from django.core.mail import SMTPConnection
+
+
+
+class SMTPQueue(SMTPConnection):
+ """
+ Can be used instead of Django's SMTPConnection class to queue
+ a bunch of emails for later sending.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(SMTPQueue, self).__init__(*args, **kwargs)
+ self._email_messages = []
+
+
+ def send_messages(self, email_messages):
+ """
+ Queues one or more EmailMessage objects and returns the number of email
+ messages queued.
+ """
+ if not email_messages:
+ return
+
+ num_sent = 0
+ for message in email_messages:
+ sent = self._queue(message)
+ if sent:
+ num_sent += 1
+ return num_sent
+
+
+ def _queue(self, email_message):
+ self._email_messages.append(email_message)
+
+
+ def defer(self):
+ """
+ Save the state of this Queue for later sending
+ """
+ try:
+ import cPickle as pickle
+ except ImportError:
+ import pickle
+
+ return pickle.dumps(self)
+
+
+ def flush(self):
+ """
+ Will send all queued EmailMessage objects.
+ """
+ if not self._email_messages:
+ return
+ new_conn_created = self.open()
+ if not self.connection:
+ # We failed silently on open(). Trying to send would be pointless.
+ return
+ num_sent = 0
+ for message in self._email_messages:
+ sent = self._send(message)
+ if sent:
+ num_sent += 1
+ if new_conn_created:
+ self.close()
+ #print "sent: %s" % num_sent
+ return num_sent
+
+
+class SMTPLoggingConnection(SMTPConnection):
+ """
+ Logs bounces etc.
+
+ """
+ def send_messages(self, email_messages):
+ """
+ Sends one or more EmailMessage objects and returns the number of email
+ messages sent.
+ """
+ from campaign.models import BounceEntry
+ if not email_messages:
+ return
+ new_conn_created = self.open()
+ if not self.connection:
+ # We failed silently on open(). Trying to send would be pointless.
+ return
+ num_sent = 0
+ for message in email_messages:
+ try:
+ sent = self._send(message)
+ except Exception, e:
+ BounceEntry.objects.create(email=message.recipients()[0], exception=str(e))
+ sent = False
+ if sent:
+ num_sent += 1
+ if new_conn_created:
+ self.close()
+ return num_sent
+

0 comments on commit 8eeb879

Please sign in to comment.