Source code for uw.local.outgoing.bin.send_mail

"""Outgoing mail handler sending process implementation.

This module implements the process which actually sends the mail messages
previously stored in the database.
"""

import os
import sys
import time
import datetime

from uw.sql.wrap import open_psycopg2_db_service_cursor

from email.encoders import encode_base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
from email.utils import *

from ..cursor import Cursor
from .loopback2 import assign_message_id, set_message_loopback, loopback_address

[docs]def get_recipients (cursor, msg_id): """Get the recipients of the specified message. :param cursor: DB connection cursor. :param int msg_id: the message ID of the message. Returns a list of recipients in a simple tuple-based layout. Each recipient is represented as (type, (name, email)). The (name, email) part of that is compatible with functions provided by the email module. """ return [(r.recipient_type, (r.recipient_name, r.recipient_email)) for r in cursor.get_recipients (msg_id=msg_id)]
[docs]def assemble_message (cursor, message_row): """Assemble an email message corresponding to a given message DB row. :param cursor: DB connection cursor. :param message_row: message as an _outgoing.mail_message row. Obtain the attachments and recipients by calling other routines, and build an email object using the standard email.mime module. Return the assembled email object. """ # Create body of message text_body = MIMEText (message_row.content.encode ('utf-8'), 'plain', 'utf-8') if message_row.content_html is None: body = text_body else: body = MIMEMultipart ('alternative') body.attach (text_body) body.attach (MIMEText (message_row.content_html, 'html', 'utf-8')) # Add attachments if needed attachments = cursor.get_attachments (msg_id=message_row.msg_id) if attachments: msg = MIMEMultipart () msg.attach (body) for attachment in attachments: item = MIMENonMultipart (attachment.content_maintype, attachment.content_subtype) item.set_payload (attachment.content, attachment.content_charset) filename = attachment.filename if filename is not None: item['Content-Disposition'] = 'attachment; filename="%s"' % filename if attachment.content_charset is None: encode_base64 (item) msg.attach (item) else: msg = body # Set main header fields msg['Date'] = message_row.completed.strftime ("%a, %d %b %Y %H:%M:%S -0400") msg['From'] = formataddr ((message_row.sender_name, message_row.sender_email)) # Process recipients recipientList = get_recipients (cursor, message_row.msg_id) recipientList.append (('Bcc', loopback_address)) recipients = {} for type, recipient in recipientList: if type not in recipients: recipients[type] = [] recipients[type].append (formataddr (recipient)) for type in recipients: msg[type] = ", ".join (recipients[type]) msg['Subject'] = message_row.subject # Assign Message-Id assign_message_id (cursor, message_row.msg_id) set_message_loopback (msg, cursor, message_row.msg_id) return msg
[docs]def send_message (cursor, message_row): """Actually send a message constructed from a given message DB row. :param cursor: DB connection cursor. :param message_row: message as an _outgoing.mail_message row. Assemble the message, then send it using /usr/lib/sendmail. Assuming sendmail completes successfully, mark the message as sent in the database. This is where the system leaves transactional control: it is possible for the message to be sent using sendmail, yet this process could crash before recording the fact that it has been sent. On the other hand, this process could record the message as sent, but sendmail or any part of the mail system could lose it entirely. """ msg = assemble_message (cursor, message_row) proc = os.popen ("/usr/lib/sendmail -t", 'w') proc.write (msg.as_string ()) result = proc.close () if result is not None: raise Exception ('Bad sendmail result: %s' % result) cursor.mark_message_sent (msg_id=message_row.msg_id) cursor.connection.commit ()
[docs]def send_message_by_id (cursor, msg_id): """Send the specified message. :param cursor: DB connection cursor. :param int msg_id: the message ID of the message. Obtain the basic message information from the database. If there is no such message, return without doing anything. If the message does exist, send it using :py:func:`send_message`. """ message_row = cursor.get_message_by_id (msg_id) if message_row is None: return send_message (cursor, message_row)
[docs]def main (): """Program entry point for mail sending cron job. Opens a database connection, then loops, obtaining and sending one message at a time until there are no more messages to send. """ cursor = open_psycopg2_db_service_cursor (cursor_class=Cursor) while True: message_row = cursor.get_message () if message_row is None: break send_message (cursor, message_row)