Chapter 8: Email和SMS

Email and SMS

Mail

Setting up email

Web2py provides the gluon.tools.Mail class to make it easy to send emails using web2py. One can define a mailer with

from gluon.tools import Mail
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'

Note, if your application uses Auth (discussed in the next chapter), the auth object will include its own mailer in auth.settings.mailer, so you can use that instead as follows:

mail = auth.settings.mailer
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'

You need to replace the mail.settings with the proper parameters for your SMTP server. Set mail.settings.login = None if the SMTP server does not require authentication.

email logging

For debugging purposes you can set

mail.settings.server = 'logging'

and emails will not be sent but logged to the console instead.

Configuring email for Google App Engine

email from GAE

For sending emails from Google App Engine account:

mail.settings.server = 'gae'

At the time of writing web2py does not support attachments and encrypted emails on Google App Engine.

x509 and PGP Encryption

PGP
x509

It is possible to send x509 (SMIME) encrypted emails using the following settings:

mail.settings.cipher_type = 'x509'
mail.settings.sign = True
mail.settings.sign_passphrase = 'your passphrase'
mail.settings.encrypt = True
mail.settings.x509_sign_keyfile = 'filename.key'
mail.settings.x509_sign_certfile = 'filename.cert'
mail.settings.x509_crypt_certfiles = 'filename.cert'

It is possible to send PGP encrypted emails using the following settings:

from gpgme import pgp
mail.settings.cipher_type = 'gpg'
mail.settings.sign = True
mail.settings.sign_passphrase = 'your passphrase'
mail.settings.encrypt = True

The latter requires the python-pyme package.

Sending emails

mail.send
email html
email attachments

Once mail is defined, it can be used to send email via:

mail.send(to=['somebody@example.com'],
          subject='hello',
          # If reply_to is omitted, then mail.settings.sender is used
          reply_to='us@example.com',
          message='hi there')

Mail returns True if it succeeds in sending the email and False otherwise. A complete argument list for mail.send() is as follows:

send(self, to, subject='None', message='None', attachments=1,
     cc=1, bcc=1, reply_to=1, encoding='utf-8',headers={})

Note, to, cc, and bcc each take a list of email addresses.

headers is dictionary of headers to refine the headers just before sending the email. For example:

headers = {'Return-Path' : 'bounces@example.org'}

Following are some additional examples demonstrating the use of mail.send().

Simple text email

mail.send('you@example.com',
  'Message subject',
  'Plain text body of the message')

HTML emails

mail.send('you@example.com',
  'Message subject',
  '<html>html body</html>')

If the email body starts with <html> and ends with </html>, it will be sent as a HTML email.

Combining text and HTML emails

The email message can be a tuple (text, html):

mail.send('you@example.com',
  'Message subject',
  ('Plain text body', '<html>html body</html>'))

cc and bcc emails

mail.send('you@example.com',
  'Message subject',
  'Plain text body',
  cc=['other1@example.com', 'other2@example.com'],
  bcc=['other3@example.com', 'other4@example.com'])

Attachments

mail.send('you@example.com',
  'Message subject',
  '<html><img src="cid:photo" /></html>',
  attachments = mail.Attachment('/path/to/photo.jpg', content_id='photo'))

Multiple attachments

mail.send('you@example.com',
  'Message subject',
  'Message body',
  attachments = [mail.Attachment('/path/to/fist.file'),
                 mail.Attachment('/path/to/second.file')])

Sending SMS messages

SMS

Sending SMS messages from a web2py application requires a third party service that can relay the messages to the receiver. Usually this is not a free service, but it differs from country to country. We have tried a few of these services with little success. Phone companies block emails originating from these services since they are eventually used as a source of spam.

A better way is to use the phone companies themselves to relay the SMS. Each phone company has an email address uniquely associated with every cell-phone number, so SMS messages can be sent as emails to the phone number.

web2py comes with a module to help in this process:

from gluon.contrib.sms_utils import SMSCODES, sms_email
email = sms_email('1 (111) 111-1111','T-Mobile USA (tmail)')
mail.send(to=email, subject='test', message='test')

SMSCODES is a dictionary that maps names of major phone companies to the email address postfix. The sms_email function takes a phone number (as a string) and the name of a phone company and returns the email address of the phone.

Using the template system to generate messages

emails

It is possible to use the template system to generate emails. For example, consider the database table

db.define_table('person', Field('name'))

where you want to send to every person in the database the following message, stored in a view file "message.html":

Dear {{=person.name}},
You have won the second prize, a set of steak knives.

You can achieve this in the following way

for person in db(db.person).select():
    context = dict(person=person)
    message = response.render('message.html', context)
    mail.send(to=['who@example.com'],
              subject='None',
              message=message)

Most of the work is done in the statement

response.render('message.html', context)

It renders the view "message.html" with the variables defined in the dictionary "context", and it returns a string with the rendered email text. The context is a dictionary that contains variables that will be visible to the template file.

If the message starts with <html> and ends with </html>, the email will be an HTML email.

Note, if you want to include a link back to your website in an HTML email, you can use the URL function. However, by default, the URL function generates a relative URL, which will not work from an email. To generate an absolute URL, you need to specify the scheme and host arguments to the URL function. For example:

<a href="{{=URL(..., scheme=True, host=True)}}">Click here</a>

or

<a href="{{=URL(..., scheme='http', host='www.site.com')}}">Click here</a>

The same mechanism that is used to generate email text can also be used to generate SMS messages or any other type of message based on a template.

Sending messages using a background task

The operation of sending an email message can take up to several seconds because of the need to log into and communicate with a potentially remote SMTP server. To keep the user from having to wait for the send operation to complete, it is sometimes desirable to queue the email to be sent at a later time via a background task. As described in Chapter 4, this can be done by setting up a homemade task queue or using the web2py scheduler. Here we provide an example using a homemade task queue.

First, in a model file within our application, we set up a database model to store our email queue:

db.define_table('queue',
    Field('status'),
    Field('email'),
    Field('subject'),
    Field('message'))

From a controller, we can then enqueue messages to be sent by:

db.queue.insert(status='pending',
                email='you@example.com',
                subject='test',
                message='test')

Next, we need a background processing script that reads the queue and sends the emails:

## in file /app/private/mail_queue.py
import time
while True:
    rows = db(db.queue.status=='pending').select()
    for row in rows:
        if mail.send(to=row.email,
            subject=row.subject,
            message=row.message):
            row.update_record(status='sent')
        else:
            row.update_record(status='failed')
        db.commit()
    time.sleep(60) # check every minute

Finally, as described in Chapter 4, we need to run the mail_queue.py script as if it were inside a controller in our app:

python web2py.py -S app -M -N -R applications/app/private/mail_queue.py

where -S app tells web2py to run "mail_queue.py" as "app", -M tells web2py to execute models, and -N tells web2py not to run cron.

Here we assume that the mail object referenced in "mail_queue.py" is defined in a model file in our app and is therefore available in the "mail_queue.py" script because of the -M option. Also notice that it is important to commit any change as soon as possible in order not to lock the database to other concurrent processes.

As noted in Chapter 4, this type of background process should not be executed via cron (except perhaps for cron @reboot) because you need to be sure that no more than one instance is running at the same time.

Note, one drawback to sending email via a background process is that it makes it difficult to provide feedback to the user in case the email fails. If email is sent directly from the controller action, you can catch any errors and immediately return an error message to the user. With a background process, however, the email is sent asynchronously, after the controller action has already returned its response, so it becomes more complex to notify the user of a failure.

 top