***** Email ***** .. XXX: Everything should be in lp.services.mail. .. module:: canonical.launchpad.mail .. module:: lp.services.mail Components for dealing with e-mail are located in :mod:`canonical.launchpad.mail` and :mod:`lp.services.mail`. Sending E-mail ============== .. module:: lp.services.mail.sendmail For most use cases, the :class:`MailController` class should be used to send e-mail from Launchpad. .. autoclass:: MailController .. automethod:: addAttachment .. automethod:: send Example:: >>> from lp.services.mail.sendmail import MailController >>> test_mail = MailController( ... 'Foo Bar ', ... ['baz@example.com'], ... 'Cool Subject', ... 'This is the content') >>> test_mail.addAttachment( ... 'Some more info', content_type='text/plain') >>> test_mail.send() Message Rationale ----------------- .. XXX: The code should make it clear that a rationale should be provided. .. XXX: INotificationRecipientSet shouldn't be in the launchpad module. .. module:: canonical.launchpad.interfaces.launchpad Each e-mail that Launchpad sent should have a rationale, for why the e-mail was sent to the receiving user. :class:`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 :class:`MailController`. To help with this, you should create a :class:`INotificationRecipientSet` instance. .. XXX: Cant use automethod on interfaces. .. autoclass:: INotificationRecipientSet() .. method:: 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. :param person: The `IPerson` or a sequence of `IPerson` that will be notified. :param reason: The rationale message that should appear in the notification footer. :param header: The code that will appear in the X-Launchpad-Message-Rationale header. .. method:: 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. :param 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. .. module:: lp.services.mail.notificationrecipientset The :class:`NotificationRecipientSet` class is a good base to use, since it provides all the necessary methods. .. autoclass:: NotificationRecipientSet 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 >> 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 :class:`IMailHandler`. .. class:: IMailHandler .. method:: process(signed_msg, to_address, filealias, log=None) Process an e-mail message. :param signed_msg: The e-mail as an `ISignedMessage`. :param 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. :param filealias: The raw byte version of the mail, as an `ILibraryFileAlias`. :param log: An optional logger, to which log messages can be sent. :return: 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 :class:`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 ', ... ['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 to baz@example.com See :doc:`in-depth/incomingmail` for a more in-depth explaination of how `handleMail()` works, and what it does.