Email

Components for dealing with e-mail are located in canonical.launchpad.mail and lp.services.mail.

Sending E-mail

For most use cases, the MailController class should be used to send e-mail from Launchpad.

class lp.services.mail.sendmail.MailController(from_addr, to_addrs, subject, body, headers=None, envelope_to=None, bulk=True)

Component for sending e-mail.

Parameters:
  • from_addr – Name and address the mail is sent from.
  • to_addrs – List of addresses to send the mail to.
  • subject – The subject of the mail.
  • body – The body of the mail, as a string.
  • headers – Dictionary of extra headers.
  • envelope_to – Optionally override the envelope address.
  • bulk – Whether to indicate that the mail is a bulk e-mail using the Precedence header.
addAttachment(content, content_type='application/octet-stream', inline=False, filename=None)

Add an attachment to the mail.

Parameters:
  • content – The content of the attachment as a byte string.
  • content_type – The mime type of the attachment.
  • inline – Whether the attachment should be displayed inline.
  • filename – The file name of the attachment, that will be used when saving the attachment to a file in the e-mail clients.
send(bulk=True)
Send away the e-mail to the mail server.

Example:

>>> from lp.services.mail.sendmail import MailController
>>> test_mail = MailController(
...     'Foo Bar <foo.bar@example.com>',
...     ['baz@example.com'],
...     'Cool Subject',
...     'This is the content')
>>> test_mail.addAttachment(
...     'Some more info', content_type='text/plain')
>>> test_mail.send()

Message Rationale

Each e-mail that Launchpad sent should have a rationale, for why the e-mail was sent to the receiving user. MailController expects the whole e-mail as it is to be sent, so the callsite is responsible for adding this information to the e-mail, before passing it on to MailController.

To help with this, you should create a INotificationRecipientSet instance.

class canonical.launchpad.interfaces.launchpad.INotificationRecipientSet

Represents a set of notification recipients and rationales.

All Launchpad emails should include a footer explaining why the user is receiving the email. An INotificationRecipientSet encapsulates a list of recipients along the rationale for being on the recipients list.

The pattern for using this are as follows: email addresses in an INotificationRecipientSet are being notified because of a specific event (for instance, because a bug changed). The rationales describe why that email addresses is included in the recipient list, detailing subscription types, membership in teams and/or other possible reasons.

The set maintains the list of IPerson that will be contacted as well as the email address to use to contact them.

add(person, reason, header)

Add a person or sequence of person to the recipients list.

When the added person is a team without an email address, all its members emails will be added. If the person is already in the recipients list, the reson for contacting him is not changed.

Parameters:
  • person – The IPerson or a sequence of IPerson that will be notified.
  • reason – The rationale message that should appear in the notification footer.
  • header – The code that will appear in the X-Launchpad-Message-Rationale header.
getReason(person_or_email)

Return a reason tuple containing (text, header) for an address.

The text is meant to appear in the notification footer. The header should be a short code that will appear in an X-Launchpad-Message-Rationale header for automatic filtering.

Parameter:person_or_email – An IPerson or email address that is in the recipients list.
Raises UnknownRecipientError:
 if the person or email isn’t in the recipients list.

The NotificationRecipientSet class is a good base to use, since it provides all the necessary methods.

class lp.services.mail.notificationrecipientset.NotificationRecipientSet
Set of recipients along the rationale for being in the set.

This class can be used to add all the people that should receive the e-mail, together with their rationale. Before sending it, you can loop through all the recipients and construct the final mail:

>>> base_mail = 'A mail.'
>>> recipients = NotificationRecipientSet()
>>> recipients.add(
...     factory.makePerson(),
...     'You received this mail because you are a subscriber',
...     'subscriber')
>>> recipients.add(
...     factory.makePerson(),
...     'You received this mail because you are the assignee',
...     'assignee')
>>> for person in recipients.getRecipients():
...     mail_reason, header_reason = recipients.getReason(person)
...     mail_with_rationale = base_mail + mail_reason
...     mail_controller = MailController(
...     'Me <me@example.com',
...     [person.preferredemail],
...     'A subject',
...     mail_with_rationale,
...     headers={'X-Launchpad-Message-Rationale': header_reason})
...     mail_controller.send()

Receiving E-mail

In order to receive and process mail, you either need to modify an existing mail handler, or create a new one. There is one handler for each domain, and they are registered in canonical.launchpad.mail.handlers:

>>> from canonical.launchpad.mail.handlers import mail_handlers
>>> for domain in sorted(mail_handlers._handlers.keys():
...     print domain
answers.lauchpad.dev
bugs.launchpad.dev
code.launchpad.dev
specs.launchpad.dev
support.launchpad.net

Each domain is handled by an IMailHandler.

class lp.services.mail.notificationrecipientset.IMailHandler
process(signed_msg, to_address, filealias, log=None)

Process an e-mail message.

Parameters:
  • signed_msg – The e-mail as an ISignedMessage.
  • to_address – The address the mail was sent to. You get all mail for a domain, so you have to look at the user part of the address to decide what to do with it.
  • filealias – The raw byte version of the mail, as an ILibraryFileAlias.
  • log – An optional logger, to which log messages can be sent.
Returns:

Return False if to_address does not exist/is bad. Return True if the message was processed, successfully or unsuccessfully. This includes user or input errors.

If you need to receive mail at a new domain, you need to create a class implementing IMailHandler, and register it with the mail_handlers object:

>>> from zope.interface import implements
>>> from canonical.launchpad.interfaces.mail import IMailHandler
>>> class ExampleHandler:
...     implements(IMailHandler)
...     allow_unknown_users = False
...     def process(self, signed_msg, to_address, filealias, log=None):
...         print "Processing mail from %s to %s." % (
...             signed_msg['From'], to_address)
...         return True

>>> from canonical.launchpad.mail.handlers import mail_handlers
>>> mail_handlers.add('example.com', ExampleHandler())

For testing, the process() method can be called directly, but if you need to really make sure that your handler is getting called, you can send a mail, and then call handleMail(), which will process all sent mail in tests:

>>> from lp.services.mail.sendmail import MailController
>>> test_mail = MailController(
...     'Foo Bar <foo.bar@example.com>',
...     ['baz@example.com'],
...     'Cool Subject',
...     'This is the content')
>>> test_mail.send()

>>> from canonical.launchpad.mail.incoming import handleMail
>>> handleMail()
Processing mail from Foo Bar <foo.bar@example.com> to baz@example.com

See How Incoming Mail is Handled for a more in-depth explaination of how handleMail() works, and what it does.

Table Of Contents

Previous topic

Overview of the Launchpad Architecture (TDB)

Next topic

How Incoming Mail is Handled

This Page