| eZ Components - Mail |
| ~~~~~~~~~~~~~~~~~~~~ |
| |
| .. contents:: Table of Contents |
| |
| |
| Introduction |
| ============ |
| |
| The Mail component provides functionality to send, retrieve and parse |
| mail messages. If you require an easy way to send a mail, use the ezcMailComposer |
| class, which allows you to send HTML mails with images, attachments and an |
| optional text part. If you require more advanced mail messages, you can build the |
| complete message yourself using the ezcMailPart derived classes. You can retrieve |
| mail messages from different sources using the supported transports. |
| |
| |
| Class overview |
| ============== |
| |
| This section gives you an overview of the main classes in the Mail |
| component. |
| |
| ezcMailComposer |
| This is a convenience class that allows you to send plain text or |
| HTML messages with attachments, without having to construct the parts of |
| the message yourself. Most users will use this class. |
| |
| ezcMail |
| If ezcMailComposer does not have the functionality you require, you can use |
| the ezcMail class to build MIME-structured mail from scratch. This requires |
| basic knowledge about how a mail is structured. |
| |
| ezcMailAddress |
| This small class represents a mail address with an optional name. It is used |
| by both ezcMailComposer and ezcMail to set recipient addresses. |
| |
| ezcMailParser |
| This class parses mail messages from text into ezcMail structures. You can |
| use it together with the mail retrieval transport classes. |
| |
| ezcMailSmtpTransport |
| Sends mails using an SMTP server. After sending a mail, the connection can be |
| kept alive so that the next mail sent uses the same connection, speeding up |
| the process. |
| |
| ezcMailMtaTransport |
| Sends mails using the PHP mail() function. |
| |
| ezcMailPop3Transport |
| Connects to a POP3 server and allows the fetching and deleting of mails. |
| |
| ezcMailImapTransport |
| Connects to an IMAP server and allows operations on mails in a mailbox |
| (fetch, delete) and operations on mailboxes (create, delete, rename, append). |
| |
| |
| Usage |
| ===== |
| |
| Transport protocols |
| ------------------- |
| |
| The Mail component provides transport protocols for both sending and retrieving |
| mail. |
| |
| For sending mail, the following protocols are supported: |
| |
| - SMTP (ezcMailSmtpTransport) - uses an SMTP server to send mail. Supports |
| plain and TLS/SSL/SSLv2/SSLv3 connections. |
| - MTA (ezcMailMtaTransport) - wraps around the PHP mail() function. |
| |
| For mail retrieval we currently support the following protocols: |
| |
| - POP3 (ezcMailPop3Transport) - an old protocol but still used. SSL is supported. |
| - IMAP (ezcMailImapTransport) - handles multiple mailboxes. SSL is supported. |
| - MBOX (ezcMailMboxTransport) - handles Unix mailbox file formats. |
| |
| Mail retrieval from other sources include: |
| |
| - File (ezcMailFileSet) - handles mails stored in files. |
| - Variable (ezcMailVariableSet) - handles mails stored in memory. |
| |
| Mail parsers |
| ------------ |
| |
| After using a mail retrieval transport to fetch a set of mails, a mail parser |
| can be used to go through the set and extract the needed information like |
| subject, sender, date and attachments from each mail in the set. The ezcMailParser |
| class is used for this purpose. |
| |
| Mail parts |
| ---------- |
| |
| The ezcMail component supports a wide variety of mail parts that can be used |
| when sending or retrieving mails: |
| |
| - ezcMailFile - mail attachment from an existing file |
| - ezcMailStreamFile - mail attachment from an open stream |
| - ezcMailVirtualFile - mail attachment from a string in memory |
| - ezcMailMultipartAlternative - used to bundle a group of mail parts where only one should be shown |
| - ezcMailMultipartDigest - used to bundle a list of mail objects |
| - ezcMailMultipartMixed - used to bundle an ordered list of mail parts |
| - ezcMailMultipartRelated - intended for mail parts consisting of several inter-related body parts |
| - ezcMailMultipartReport - used for sending delivery status notifications |
| - ezcMailDeliveryStatus - used for sending delivery status notifications |
| - ezcMailRfc822Digest - used to insert another mail into a mail |
| - ezcMailText - used for plain text |
| |
| Mail tools |
| ---------- |
| |
| In the ezcMailTools class, you will find various useful static methods that can be |
| used in your applications: |
| |
| - ezcMailTools::lineBreak() - returns one end-of-line character (default \\r\\n). Use ezcMailTools::setLineBreak() to change the default |
| - ezcMailTools::composeEmailAddress() - returns the RFC822 representation of a mail address as a string. Use ezcMailTools::composeEmailAddresses() for an array of mail address objects |
| - ezcMailTools::parseEmailAddress() - returns an ezcMailAddress object from a string mail address. Use ezcMailTools::parseEmailAddresses() for a string of mail addresses |
| - ezcMailTools::generateMessageId() - returns a unique message ID to be used for a mail message |
| - ezcMailTools::generateContentId() - returns a unique ID to be used for Content-ID headers |
| - ezcMailTools::mimeDecode() - decodes MIME-encoded fields and tries to recover from errors |
| - ezcMailTools::replyToMail() - returns a new ezcMail object that is a reply to the specified ezcMail object |
| |
| See the ezcMailTools example below for information on how to use these methods. |
| |
| |
| Building and sending mail |
| ========================= |
| |
| eZ components provides two ways to create mail. The simplest is to use the |
| composer class ezcMailComposer. Using the composer you can send plain text |
| messages, HTML messages with images and messages with attachments. |
| If you require more advanced messages you can also customize them entirely by |
| building it from the scratch using the various part types in ezcMail. The part |
| types are structured the same way as the underlying mail MIME types. |
| |
| Sending a mail with the composer |
| -------------------------------- |
| |
| Sending a mail using the composer is very straightforward. This small example |
| displays how to send a normal text message. |
| |
| .. include:: tutorial/tutorial_composer.php |
| :literal: |
| |
| Sending a mail with HTML, inline images and attachments |
| ------------------------------------------------------- |
| |
| This example shows how to send a mail with HTML text, images and attachments |
| using the ezcMailComposer class. |
| |
| .. include:: tutorial/tutorial_composer_attachments.php |
| :literal: |
| |
| The mime-type of the attachment can be specified if needed. See the |
| documentation for the functions addFileAttachment() and addStringAttachment() |
| for more details. |
| |
| Securing HTML mails which include file:// in image tags |
| ------------------------------------------------------- |
| |
| By default, if the htmlText property contains an HTML image tag with file:// |
| in href, that file will be included in the created message. |
| |
| Example:: |
| |
| <img src="file:///home/me/image.jpg" /> |
| |
| This can be a security risk if a user links to another file, for example logs |
| or password files. With the automaticImageInclude option (default value true) |
| from ezcMailComposerOptions, the automatic inclusion of files can be turned |
| off. |
| |
| Example:: |
| |
| $options = new ezcMailComposerOptions(); |
| $options->automaticImageInclude = false; // default value is true |
| |
| $mail = new ezcMailComposer( $options ); |
| |
| // ... add To, From, Subject, etc to $mail |
| $mail->htmlText = "<html>Here is the image: <img src="file:///etc/passwd" /></html>"; |
| |
| // ... send $mail |
| |
| After running the above code, the sent mail will not contain the file specified |
| in the htmlText property. |
| |
| Building a mail from scratch |
| ---------------------------- |
| |
| The class structure of the Mail component follows that of the mail MIME. This |
| means that you can build advanced MIME messages part by part. |
| |
| The first example displays how to build a similar message to the one above. |
| |
| .. include:: tutorial/tutorial_mail_simple.php |
| :literal: |
| |
| As you can see, there is not much difference compared to the composer version. |
| In the next example we will add an attachment to our manually built mail: |
| |
| .. include:: tutorial/tutorial_mail_attachments.php |
| :literal: |
| |
| By default the generated message will contain a text which is displayed by |
| e-mail clients lacking MIME-support:: |
| |
| This message is in MIME format. Since your mail reader does not understand |
| this format, some or all of this message may not be legible. |
| |
| This text can be changed or removed by setting the property *noMimeMessage* on |
| the multipart, before generating the mail or sending it. Example:: |
| |
| $part = new ezcMailMultipartMixed( $textPart, $fileAttachment ); |
| $part->noMimeMessage = 'Your e-mail client does not understand MIME.'; |
| |
| $mail = new ezcMail(); |
| $mail->body = $part; |
| |
| Building MIME structures that work |
| ---------------------------------- |
| |
| When you build mail mail from scratch most combinations of MailParts will |
| produce valid messages. Unfortunately, even though a message is valid |
| structurally that does not mean that all mail clients will display it |
| properly. This section gives a few hints on what to do and what not to do. |
| |
| 1. Ommit Multipart/Mixed parts with only one part. Some mail clients like |
| Mozilla Thunderbird do not display these correctly. Of course, they are not |
| necessary either. |
| |
| 2. Mail with alternative text/HTML parts and common attachments can be |
| implemented in many ways. However, we have only found one structure that |
| seems to work across all clients: |
| MultipartMixed( MultipartAlternative( TextPart, TextPart ), FilePart, ... ) |
| |
| |
| Sending a mail using SMTP |
| ------------------------- |
| |
| This example shows how to send a mail with SMTP, by using an SSLv3 |
| connection: |
| |
| .. include:: tutorial/tutorial_smtp_ssl.php |
| :literal: |
| |
| |
| Using stronger authentication methods with the SMTP transports |
| -------------------------------------------------------------- |
| |
| The SMTP transports supports various authentication methods: DIGEST-MD5, |
| CRAM-MD5, NTLM, LOGIN, PLAIN. Not all methods are supported by all servers, and |
| some servers don't support authentication at all. NTLM authentication requires |
| the mcrypt PHP extension. |
| |
| By default, the SMTP transport tries to login anonymously to the SMTP server |
| (if an empty username and password have been provided), or to authenticate with |
| the strongest method supported by the server (if username and password have |
| been provided). The preferred authentication method can be changed with the |
| option preferredAuthMethod. See the ezcMailSmtpTransport class for a list of |
| supported authentication methods. |
| |
| If the preferred method is specified via options, only that authentication |
| method will be attempted on the SMTP server. If it fails, an exception will be |
| thrown. |
| |
| .. include:: tutorial/tutorial_smtp_auth.php |
| :literal: |
| |
| Avoid Bcc headers appearing in the sent mails |
| --------------------------------------------- |
| |
| By default the Mail component leaves it to the SMTP server to strip the Bcc |
| header from the sent mails. Some SMTP servers will not strip the Bcc header. If |
| you want to be sure that the Bcc header will not appear in sent mails, use |
| the stripBccHeader option from ezcMailOptions:: |
| |
| $options = new ezcMailOptions(); |
| $options->stripBccHeader = true; // default is false |
| |
| $mail = new ezcMail( $options ); |
| |
| Character encoding |
| ------------------ |
| |
| Most of the world does not speak and write US ASCII and thus requires more |
| advanced character encoding to display mail correctly. |
| |
| The following example shows how to send a mail with the body and subject |
| encoded with iso-8859-1 and a custom header encoded with iso-8859-1: |
| |
| .. include:: tutorial/tutorial_charset.php |
| :literal: |
| |
| You can of course choose and combine any available character sets. Make sure |
| that the input text is encoded as specified, or you may get unexpected |
| results. |
| |
| Extending the Mail component |
| ---------------------------- |
| |
| It is possible to extend the Mail component if you require part types that are |
| not supported by default. The following two examples shows how you can |
| implement support for digest mail messages as attachments to your mail. This |
| functionality is available through the ezcMailRfc822Digest class. For |
| the sake of this example, we will recreate it in the MailRFC822Digest class. |
| |
| The mail system already supports sending attachments through the |
| ezcMailMultipartMixed type. Unfortunately directly inserting an ezcMail object |
| as a part does not work. This is because mail digests are a special case: they |
| require two extra headers that are separated by the normal mail headers. |
| |
| To make it work, we create the class RFC822Digest to add these headers: |
| |
| .. include:: tutorial/tutorial_extend_create.php |
| :literal: |
| |
| Our new class extends the ezcMailPart class. This is required for all parts of |
| a mail. ezcMailPart provides two important methods that we can override: |
| ezcMailPart::generateHeaders() and ezcMailPart::generateBody(). These two |
| methods are called in succession by the parent part and should return the |
| headers and the body text of the part. |
| |
| We do not need to override generateHeaders() since we can simply set the headers |
| we want directly on the object. We do need to override generateBody(), |
| since we want to include the full text of the mail digest. |
| |
| The new class can be used directly when building a mail. The example assumes |
| that a valid ezcMail object is available in the $digest variable. |
| |
| .. include:: tutorial/tutorial_extend_use.php |
| :literal: |
| |
| Using the ezcMailTools class |
| ---------------------------- |
| |
| In this example, we use the various methods from the ezcMailTools class. |
| |
| .. include:: tutorial/tutorial_tools.php |
| :literal: |
| |
| |
| Mail retrieval and parsing |
| ========================== |
| |
| Many applications need to interact with a message store. The Mail |
| component makes this easy through the class ezcMailParser and the mail |
| retrieval transport classes. Mail is fetched, parsed and returned to you in the |
| same structure that is used to send mail. |
| |
| The Mail component currently allows you to fetch and parse mail messages from |
| POP3, IMAP, mbox files, single mail files and from variables. The parser fully |
| supports mail in all character sets, multipart mail (attachments), HTML mail, |
| HTML mail with images and digest messages. |
| |
| Retrieving mail using POP3 |
| -------------------------- |
| |
| The following example shows how to fetch messages from a POP3 account using |
| various methods and to parse the messages for use. |
| |
| .. include:: tutorial/tutorial_pop3.php |
| :literal: |
| |
| The parser returns an array of ezcMail messages with parts organized according |
| to the MIME structure of the mail. |
| |
| Retrieving mail using IMAP |
| -------------------------- |
| |
| The following example shows how to fetch messages from an IMAP account using |
| various functions and to parse the messages for use. |
| |
| .. include:: tutorial/tutorial_imap.php |
| :literal: |
| |
| The parser returns an array of ezcMail messages with parts organized according |
| to the MIME structure of the mail. |
| |
| Additional usage of the IMAP transport |
| -------------------------------------- |
| |
| The IMAP transport supports multiple mailboxes. In the following example, we |
| work with mailboxes and flags. |
| |
| .. include:: tutorial/tutorial_imap_extra.php |
| :literal: |
| |
| Refering messages by their unique IDs in IMAP |
| --------------------------------------------- |
| |
| With IMAP it is possible to refer to messages using their unique IDs, which |
| usually never change, unlike message numbers. Unfortunately this is not |
| possible yet in POP3. |
| |
| The next example shows how to enable refering to messages by their unique IDs. |
| To see the list of the methods which support unique IDs referencing, consult |
| the documentation for the ezcMailImapTransport class. |
| |
| .. include:: tutorial/tutorial_imap_uids.php |
| :literal: |
| |
| Working with transport options |
| ------------------------------ |
| |
| The POP3, IMAP and SMTP transports allow options to be specified when calling |
| the transport constructors. These options are implemented in the classes |
| ezcMailPop3TransportOptions, ezcMailImapTransportOptions and |
| ezcMailSmtpTransportOptions. In the following example, we |
| specify options when calling the POP3 transport constructor. |
| |
| .. include:: tutorial/tutorial_pop3_options.php |
| :literal: |
| |
| Using SSL with POP3 and IMAP |
| ---------------------------- |
| |
| The POP3 and IMAP transports allow SSL connections (if the mail server supports |
| them). In the following example, we connect to an IMAP server |
| using an SSL connection. |
| |
| .. include:: tutorial/tutorial_imap_ssl.php |
| :literal: |
| |
| Retrieving mail from mbox files |
| ------------------------------- |
| |
| The following example shows how to fetch all messages from an mbox file and |
| to parse the messages for use. |
| |
| .. include:: tutorial/tutorial_mbox.php |
| :literal: |
| |
| The parser returns an array of ezcMail messages with parts organized according |
| to the MIME structure of the mail. |
| |
| Parsing a message set |
| --------------------- |
| |
| The following example shows how to parse a message set retrieved from an IMAP |
| or POP3 account, an mbox file, a single mail file or a variable. |
| |
| .. include:: tutorial/tutorial_parse.php |
| :literal: |
| |
| Because the parser will delete the temporary attachments after the script ends, |
| it is needed to save those files to another directory. On lines 19-26 a way of |
| how to do this is shown. The *contentDisposition->displayFileName* property of |
| $part was used, as the *fileName* property of $part contains the raw file name |
| from the mail. |
| |
| For a more detailed example on how to use a mail object, please see the |
| `display example`_. |
| |
| .. _display example: Mail_display-example.html |
| |
| For an example on how to display a listing of mails, please see the |
| `mail listing example`_. |
| |
| .. _mail listing example: Mail_mail-listing-example.html |
| |
| For a list of supported mail-related RFCs, please see the `RFCs list`_. |
| |
| .. _RFCs list: Mail_rfcs.html |
| |
| Displaying HTML mail with inline images |
| --------------------------------------- |
| |
| In order to display HTML mail with inline images, you need to make sure that |
| the attached images are accessible by the web server. Besides this, you also |
| need to replace the internal mail references to the images with references to |
| the file in the document root. In the following example we use |
| ezcMailTools::replaceContentIdRefs() to do exactly this. |
| |
| .. include:: tutorial/tutorial_html_display.php |
| :literal: |
| |
| Using a custom mail class |
| ------------------------- |
| |
| When parsing a mail, an object of class ezcMail is created by default. You can |
| change this by specifying a custom mail class to the parser before parsing. The |
| custom mail class must extend ezcMail. |
| |
| Example:: |
| |
| $parser = new ezcMailParser(); |
| $parser->options->mailClass = 'myCustomMailClass'; // extends ezcMail |
| $parser->parseMail( $set ); |
| |
| Using a custom file class |
| ------------------------- |
| |
| When parsing a mail, an object of class ezcMailFile is created by default for |
| each file attachment. You can change this by specifying a custom file class to |
| the parser before parsing. The custom file class must extend ezcMailFile. |
| |
| Example:: |
| |
| $parser = new ezcMailParser(); |
| $parser->options->fileClass = 'myCustomFileClass'; // extends ezcMailFile |
| $parser->parseMail( $set ); |
| |
| |
| Troubleshooting |
| =============== |
| |
| MTA: Qmail |
| ---------- |
| |
| Qmail insists on only using "\\n" line breaks and will send garbled messages |
| with the default "\\r\\n" setting. To fix this issue, use |
| ezcMailTools::setLineBreak( "\\n" ) before sending mail. |
| |
| MTA: Sendmail relaying denied |
| ----------------------------- |
| |
| This can happen when the SMTP server you try to use has disabled |
| the sending of mail from computers not connected to its network, or |
| if it requires authentication. Talk to the administrator of the SMTP server to |
| see what the requirements are to send mail. |
| |
| Check also that sendmail is installed and configured correctly. |
| |
| For Windows, you need to specify a valid SMTP server in php.ini, or you can |
| download a "fake" sendmail from the internet. |
| |
| IMAP: Authentication failed |
| --------------------------- |
| |
| Sometimes the IMAP transport fails to authenticate, in which case the |
| authenticate() method will return false. The application should detect when |
| this occurs and attempt authentication again (for example, for a preset |
| number of times such as three). |
| |
| IMAP: Could not read from the stream |
| ------------------------------------ |
| |
| While using the IMAP methods, it is possible that an ezcMailTransportException |
| is thrown, in which case the connection to the IMAP server is closed. The |
| application should catch this exception and decide how to handle this |
| situation (show an error, reconnect). |
| |
| Parsing: iconv() notices |
| ------------------------ |
| |
| If the mail that you try to parse is not encoded properly, the `iconv`_ () |
| function will throw notices (from the function convertToUTF8Iconv() in |
| ezcMailCharsetConverter). |
| |
| To avoid the notices you can use your own conversion function: |
| |
| 1. Create a new function which is similar to convertToUTF8Iconv() from |
| ezcMailCharsetConverter, but which supresses notices and errors (with @ in |
| front of `iconv`_ ()): :: |
| |
| class myConverter |
| { |
| public static function convertToUTF8IconvNoNotices( $text, $originalCharset ) |
| { |
| if ( $originalCharset === 'unknown-8bit' || $originalCharset === 'x-user-defined' ) |
| { |
| $originalCharset = "latin1"; |
| } |
| return @iconv( $originalCharset, 'utf-8', $text ); |
| } |
| } |
| |
| 2. Use the created function instead of the normal one (set this before parsing |
| mail): :: |
| |
| ezcMailCharsetConverter::setConvertMethod( array( 'myConverter', 'convertToUTF8IconvNoNotices' ) ); |
| |
| Parsing: missing characters |
| --------------------------- |
| |
| If the mail that you try to parse is not encoded properly, the `iconv`_ () |
| function will throw notices (from the function convertToUTF8Iconv() in |
| ezcMailCharsetConverter). |
| |
| To avoid the missing characters you can use your own conversion function: |
| |
| 1. Create a new function which is similar to convertToUTF8Iconv() from |
| ezcMailCharsetConverter, but which uses one of the options //IGNORE or |
| //TRANSLIT for `iconv`_ (): :: |
| |
| class myConverter |
| { |
| public static function convertToUTF8IconvIgnore( $text, $originalCharset ) |
| { |
| if ( $originalCharset === 'unknown-8bit' || $originalCharset === 'x-user-defined' ) |
| { |
| $originalCharset = "latin1"; |
| } |
| return iconv( $originalCharset, 'utf-8//TRANSLIT', $text ); |
| } |
| } |
| |
| 2. Use the created function instead of the normal one (set this before parsing |
| mail): :: |
| |
| ezcMailCharsetConverter::setConvertMethod( array( 'myConverter', 'convertToUTF8IconvIgnore' ) ); |
| |
| See the other examples in ezcMailCharsetConverter, and see also the |
| documentation for the `iconv`_ () function to find out how //IGNORE and |
| //TRANSLIT work. |
| |
| Parsing: broken headers |
| ----------------------- |
| |
| If the mail contains headers with broken text (eg. 8-bit characters with no |
| character set specified), then the parsed header will contain broken text or |
| missing characters. |
| |
| For example, when parsing a mail with this header:: |
| |
| Subject: Un Fax a été émis |
| |
| Then $mail->subject will contain 'Un Fax a t mis' (because the 8-bit characters |
| could not be parsed correctly by ezcMailTools::mimeDecode() due to the |
| missing character set). |
| |
| In order to be able to get the correct value of the header, use |
| $mail->getHeader( 'Subject' ), which is the raw value of the header |
| (MIME-encoded). Other headers are available as raw through the getHeader() |
| function. |
| |
| The raw headers (obtained via the function getHeader of ezcMailPart) are |
| MIME-encoded for non-broken headers (eg. '=?iso-8859-1?Q?=E6=F8=E5?='), so if |
| you want to decode them use ezcMailTools::mimeDecode() or implement your own |
| MIME-decoding function. |
| |
| Parsing: multiple-value headers |
| ------------------------------- |
| |
| Mail headers such as **Received** can appear multiple times in a mail message. |
| By default the mail parser will only recognize the first value of the header |
| it encounters, and will disregard the others. |
| |
| To retrieve all the values of a header from a mail in an array, use the second |
| argument for the function $mail->getHeader() with the value *true*. |
| |
| Example:: |
| |
| $mail->getHeader( 'Received', true ); |
| |
| This will return an array which looks like this:: |
| |
| array( |
| "from punisher.example.com (punisher.example.com...", |
| "from localhost (localhost [127.0.0.1])...", |
| "from punisher.example.com ([127.0.0.1])...", |
| "from www.example.com (unknown [216.198.224.130]) by...", |
| "from localhost (localhost) by www.example.com..." |
| ); |
| |
| instead of only the first value as a string if the second parameter to |
| getHeader() was not specified. |
| |
| Parsing: text attachments as file parts |
| --------------------------------------- |
| |
| By default, the mail parser creates ezcMailTextPart objects for text |
| attachments found in the mail. |
| |
| To create ezcMailFile objects instead (for example on servers where |
| internal memory is limited), set this option to the mail parser before |
| parsing:: |
| |
| $parser = new ezcMailParser(); |
| $parser->options->parseTextAttachmentsAsFiles = true; |
| // call $parser->parseMail( $set ); |
| |
| Parsing: attachment file names |
| ------------------------------ |
| |
| When parsing a mail with attachments you can use a code similar to the one in |
| section _`Parsing a message set`. The mail part that contains the attachment |
| ($part) has the *fileName* property which contains the raw file name, |
| *contentDisposition->displayFileName* which contain the MIME-decoded file name. |
| Use the *contentDisposition->displayFileName* value to copy the temporary files |
| saved during parsing which contain the attachments, and which will not be |
| available on the next request. |
| |
| .. _iconv: http://php.net/manual/en/function.iconv.php |
| |
| |
| |
| .. |
| Local Variables: |
| mode: rst |
| fill-column: 79 |
| End: |
| vim: et syn=rst tw=79 |