the djb way

qmail


the big qmail flowchart

The qmail source distribution includes nine PIC.* files. Collectively, these files provide an excellent picture of how qmail processes incoming messages.

When configuring qmail for specific requirements --[such as the Radio Email project in West Africa]-- it is essential to understand this picture, with a clear idea about what decisions qmail makes at each stage of processing. This page represents an effort to synthesize the PIC.* files from another perspective, and describe an overall flow chart for qmail.

This chart is broken down into three sections:

  1. Local vs. remote delivery.

  2. Remote delivery.

  3. Local delivery.

All in glorious ascii art!


local vs. remote delivery

qmail can accept a message for delivery from any of several sources:

Once a message has been accepted, the first decision qmail must make (by qmail-send), is whether to queue the message for local or remote delivery. In order to make this decision, it examines the recipient address:

someuser@somehost

This address is broken down into two main parts, according to what appears before and after the @ symbol:

USER=someuser
HOSTNAME=somehost

qmail then applies the tests and decision tree as outlined in the following diagram:

+----------------------------+
| control/locals             |
|                            |
|   match for HOSTNAME?      |        YES
|                            |----------------------->|
+----------------------------+                        |
            |                                         |
            | NO                                      |
            |                                         |
            V                                         |
+----------------------------+                        |
| control/virtualdomains     |                        |
|                            |                        |
|   match for USER@HOSTNAME? |        YES             |
|   match for HOSTNAME?      |----------------------->|
+----------------------------+                        |
            |                                         |
            | NO                                      |
            |                                         |
            V                                         V
+============================+            +============================+
!     REMOTE DELIVERY        !            !     LOCAL DELIVERY         !
+============================+            +============================+

Here we see that qmail first checks the control file locals, to find any match for HOSTNAME. If found, it passes the message to qmail-lspawn for local delivery.

Otherwise, qmail tests the control file virtualdomains. Here it looks for an entry matching the form:

USER@HOSTNAME:localuser

or:

HOSTNAME:localuser

If found, it passes the message for local delivery to qmail-lspawn as an extension address of the form:

localuser-USER

(Hopefully this diagram makes it clear why virtual domains should not also be entered into the control/locals control file.)

Otherwise, qmail decides the message should be passed to qmail-rspawn for remote delivery.


remote delivery

Once a message has been queued for remote delivery, qmail takes a decision path as outlined in this diagram:

+----------------------------+
| control/smtproutes         |
|                            |
|   match for HOSTNAME?      |          YES (IP)
|                            |----------------------->|
+----------------------------+                        |
            |                                         |
            | NO                                      |
            |                                         |
            V                                         |
+----------------------------+                        |
|                            |                        |
|   dnsmx HOSTNAME?          |          YES (IP)      |
|                            |----------------------->|
+----------------------------+                        |
            |                                         |
            | NO                                      |
            |                                         |
            |                                         V
            |                             +----------------------------+
            |                             |                            |
            |                  NO         | SMTP delivery IP, success? |
            |<----------------------------|                            |
            |                             +----------------------------+
            |                                         |
            |                                         | YES
            |                                         |
            V                                         V
+============================+                     ========
! control/queuelifetime      !                       done!
!                            !                     ========
!   defer delivery or        !
!   BOUNCE                   !
+============================+

Here qmail-remote first checks the smtproutes control file for any entry where HOSTNAME matches the DOMAIN expression of the form:

DOMAIN:relayhost

There are several ways HOSTNAME may match DOMAIN through wildcard expressions, including:

:relayhost

In this case, all remote-bound mail will be attempted to relayhost.

Otherwise, if no match in smtproutes is found, qmail-remote will perform DNS lookups for a mail-exchange (MX) record for HOSTNAME.

qmail-remote will make repeated attempts to deliver the message to the remote host, until the time in control/queuelifetime has been reached. Then, if the message has still not been delivered, it is bounced back to the sender.

Note: once qmail has made its decision to queue for remote delivery, that's it. The message is queued for remote delivery. Sometimes this isn't what you want. For example, you forgot (or mispelled) an entry in control/locals for a domain that should be considered local.

What to do? You could just wait for the message to bounce. Instead:

  1. Fix control/locals with the correct entry for HOSTNAME.

  2. Make a temporary entry in control/smtproutes, forcing delivery for HOSTNAME to localhost.

  3. Give qmail-send a SIG-ALRM:

    # svc -a /service/qmail-send
    
  4. After the queue has cleared, remove the temporary entry from control/smtproutes.


local delivery

Once a message has been queued for local delivery, qmail executes a decision tree similar to the following:

+----------------------------+
| /var/qmail/users/cdb       |
|                            |
|   match for USER?          |    YES     +============================+
|                            |----------->! setup for qmail-local      !
+----------------------------+            +============================+
            |                                         |
            | NO                                      |
            |                                         |
            V                                         |
+----------------------------+                        |
| /etc/passwd                |                        |
|                            |                        V
|   account for USER?        |    YES     +-----------------------------+
|   uid USER nonzero?        |----------->| ~USER exists?               |
+----------------------------+            | ~USER owned by uid USER?    |
            |                             +-----------------------------+
            | NO                                |                  |
            |                                   | YES              | NO
            V                                   V                  :
+----------------------------+            +-----------------------------+
| /var/qmail/alias           |            | ~USER                       |
|                            |            |                             |
| if USER:                   |            | if USER:                    |
|   .qmail-USER?             |            |   .qmail?                   |
|   .qmail-default?          |            |   .qmail-default?           |
|   default delivery?        |            |   default delivery?         |
|                            |            |                             |
| if USER-EXT:               |            | if USER-EXT:                |
|   .qmail-USER-EXT?         |            |   .qmail-EXT?               |
|   .qmail-USER-default?     |            |   .qmail-EXT-default?       |
|   .qmail-default?          |            |   .qmail-default?           |
|   default delivery?        |            |   default delivery?         |
+----------------------------+            +-----------------------------+
            |           |                      |                   |
            | NO        | YES              YES |                   | NO
            |           \----------------------/                   |
            |                       |                              |
            V                       V                              V
       ==========              ===========       +============================+
         BOUNCE                  deliver         ! control/queuelifetime      !
       ==========              ===========       !                            !
                                                 !   defer delivery or        !
                                                 !   BOUNCE                   !
                                                 +============================+

First qmail-lspawn checks the special qmail-users database in /var/qmail/users/cdb. This database provides a fine-grained mechanism for routing local mail deliveries, as an alternative to the standard /etc/passwd mechanism. (We don't discuss it further here; see the man page for qmail-users(5) for more information.)

Then qmail checks /etc/passwd for an account matching USER. If found, there are several additional constraints:

Once these constraints have been met, control is passed to qmail-local for the delivery. Here qmail searches for delivery instructions in the user's home directory according to the dot-qmail conventions, and according to whether the address has an extension, in the .qmail files as shown. If no dot-qmail delivery instructions are found, qmail attempts the default delivery instructions as provided to qmail-start in the qmail-send run script.

If for some reason the message to USER is undeliverable, qmail will keep trying until the time in control/queuelifetime has expired. Some of the conditions that may temporarily prevent delivery include:

If the faulty conditions are corrected, the mail will be delivered on the next attempt.

If no account for USER was found in /etc/passwd, or if USER has a uid=0, then control is passed to qmail-local to attempt delivery to the special alias user. Again, the dot-qmail conventions are followed, now according to the .qmail files found within /var/qmail/alias.

The special alias user is qmail's user of last resort. If a message is not deliverable to alias, then the message is considered undeliverable.

The bounce you hear next is the process starting all over again from the top, to return the message back to the sender...


Copyright © 2002, 2003, Wayne Marshall.
All rights reserved.

Last edit 2003.12.31, wcm.