Deliver a Test Message Safely with Dovecot LDA
An MTA that "works" but never actually lands mail in the mailbox is a classic first day of chasing a broken transport. This guide delivers one RFC 5322 message straight through dovecot-lda, reads the result from its exit status, and shows you where to look when delivery is rejected or quietly falls back to INBOX. The commands target the Dovecot 2.3 build shipped by Ubuntu's dovecot-core package, version 1:2.3.21+dfsg1-2ubuntu6.5. Allow 10 minutes if Dovecot and the mailbox already work, longer if you are wiring up an MTA transport from scratch.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
Dovecot LDA is a delivery component, not an SMTP server. An MTA normally hands it a complete message on standard input and names the destination user; a shell test can do exactly the same, but it changes mailbox state immediately. Do not point the examples at a real recipient until every placeholder is replaced and you have checked the effective configuration.
- You need the
dovecot-corepackage, a configured mailbox location, and permission to deliver as the account or virtual user. - The executable here is
/usr/lib/dovecot/dovecot-lda. The installed manual also calls itdeliver, but that name is not the path you should actually run.
1. Check the executable and configuration
Start with a read-only check. The built-in usage output is worth having, since distributions can install this binary outside PATH:
$ command -v /usr/lib/dovecot/dovecot-lda
/usr/lib/dovecot/dovecot-lda
$ /usr/lib/dovecot/dovecot-lda --help
/usr/lib/dovecot/dovecot-lda: invalid option -- '-'
Usage: dovecot-lda [-c <config file>] [-d <username>] [-p <path>]
[-m <mailbox>] [-e] [-k] [-f <envelope sender>]
[-a <original envelope recipient>]
[-r <final envelope recipient>]
This program has no conventional --help, so that diagnostic is expected behaviour on the checked 2.3.21 binary, not a failure. For the settings actually in effect, ask Dovecot's own configuration tool as an administrator:
# doveconf -n
mail_location = maildir:~/Maildir
Check especially mail_location, the user database, and the LDA settings in /etc/dovecot/conf.d/15-lda.conf. The sample here uses maildir:~/Maildir, but your system may use mbox, a virtual-user path, or another namespace entirely.
2. Prepare a message without sending it anywhere
Write the message to a temporary file so you can see exactly what LDA will receive. This is data for LDA, not an SMTP submission, so it needs the headers and the blank line before the body:
$ umask 077
$ test_mail=$(mktemp)
$ cat > "$test_mail" <<'EOF'
From: [email protected]
To: [email protected]
Subject: Dovecot LDA test
Date: Thu, 01 Jan 1970 00:00:00 +0000
Message-ID: <[email protected]>
This is a local delivery test.
EOF
$ sed -n '1,12p' "$test_mail"
From: [email protected]
To: [email protected]
Subject: Dovecot LDA test
Date: Thu, 01 Jan 1970 00:00:00 +0000
Message-ID: <[email protected]>
This is a local delivery test.
Replace [email protected] with an address your Dovecot user database actually understands. The .invalid domain is deliberately non-routable, which makes it a safe header, but it is not a substitute for using the real local recipient.
3. Deliver to the user's default mailbox
Warning
This is the first state-changing command. It consumes the message into the selected user's mailbox for real. Check the recipient and configuration before you run it.
$ /usr/lib/dovecot/dovecot-lda \
-d '[email protected]' \
-f '[email protected]' < "$test_mail"
$ status=$?
$ printf 'dovecot-lda exit status: %s\n' "$status"
dovecot-lda exit status: 0
-dasks the user database for the destination user. Normally needed for virtual users.-fsets the envelope sender. Dovecot uses it to construct a rejection message or related delivery metadata; it is not the visibleFrom:header.- The default destination is
INBOX. Nothing else, unless you say otherwise.
Checkpoint
Exit status 0 means LDA accepted the message for delivery, not that an SMTP conversation took place. Dovecot updates its mailbox indexes as part of that delivery, which is exactly why mail should go through LDA rather than being written straight into a Maildir.
4. Select a mailbox deliberately
Use -m when the message belongs somewhere other than INBOX. The name is the mailbox name visible through the configured namespace:
$ /usr/lib/dovecot/dovecot-lda \
-d '[email protected]' \
-a '[email protected]' \
-m 'Alerts' \
-f '[email protected]' < "$test_mail"
$ printf 'dovecot-lda exit status: %s\n' "$?"
dovecot-lda exit status: 0
Here -a records the destination address, plus detail included. If Alerts does not exist, LDA will not create it unless lda_mailbox_autocreate = yes is set.
Tip
If saving to the requested mailbox fails for any other reason, the installed manual says LDA falls back to delivering to INBOX instead. That fallback can make a test look like it passed when it did not go where you meant it to, so check the actual mailbox with an IMAP client or a mailbox-aware Dovecot tool. For a namespace such as INBOX/, use the same separator and visible name your IMAP client uses; do not guess filesystem directory names from a Maildir listing.
5. Read failures from the exit status
Capture the status immediately, before another command overwrites it. The useful values for this Dovecot 2.3 interface:
| Status | Meaning | Next check |
|---|---|---|
| 0 | Delivery succeeded. | Confirm the message is in the intended mailbox. |
| 64 | An argument or other parameter was invalid. | Compare the command with the usage output and quote values. |
| 75 | Temporary failure. | Read the Dovecot log and check mailbox permissions, storage and indexes. |
| 77 | Rejection when -e was used. | Read stderr and investigate quota or policy before retrying. |
For an MTA integration, status 75 normally tells the MTA to retry. Do not turn every non-zero status into a permanent bounce without checking the MTA's delivery contract first. The default rejection behaviour is to send a rejection mail; adding -e instead writes the reason to stderr and returns 77, which changes how the calling MTA handles policy and quota failures:
$ /usr/lib/dovecot/dovecot-lda -e -d '[email protected]' \
-f '[email protected]' < "$test_mail"
$ printf 'dovecot-lda exit status: %s\n' "$?"
dovecot-lda exit status: 77
The exact status and rejection text depend on the configured user, quota and plugins. Treat the example's 77 as the expected shape of a rejected delivery, not something to force on a mailbox that is otherwise healthy.
6. Use a saved message only when needed
The -p option makes LDA read from a path instead of standard input:
$ /usr/lib/dovecot/dovecot-lda \
-d '[email protected]' \
-p "$test_mail"
$ printf 'dovecot-lda exit status: %s\n' "$?"
dovecot-lda exit status: 0
With Maildir, Dovecot may hard-link this file into the destination rather than copy it. The installed manual warns that this path currently prevents cache updates, so standard input stays the safer default for normal MTA delivery. A hard link also means cleaning up the original file too early can remove an inode you still expected to inspect.
Recovery
The examples deliberately create a temporary file and add messages to a mailbox. Remove only the named temporary file when you are finished, and delete the test message through the normal IMAP client or mailbox administration procedure. Do not remove Maildir files by wildcard, and do not hand-edit index files.
$ rm -- "$test_mail"
$ test ! -e "$test_mail" && echo 'temporary input removed'
temporary input removed
Done means
- Configuration checked.
doveconf -nshows the mailbox location and user lookup you intended. - Message delivered properly. It was passed as complete input to
dovecot-lda, not copied directly into a Maildir. - Landing confirmed. The command returned
0and the message is visible in the intended mailbox, not merely INBOX. - MTA integration checked. Temporary failures are preserved and LDA stderr is recorded in its logs.
-eunderstood. You know whether it is compatible with your MTA's rejection handling before switching it on.