Whilst the direct method from within PHP via an external SMTP server (such as Mailgun, SendGrid or the customer’s Office 365/Google Workspace account) is quick to set up, it has significant drawbacks in a production environment. These services may sometimes be unavailable and are often (very, very) slow. If a workflow (such as logging in, placing an order in the shop, submitting a form or creating a forum post) is held up whilst waiting for an email to be sent, this can lead to long waiting times for visitors, frustration and, consequently, them abandoning the action.
The solution is a lightweight, containerised Postfix MTA (Mail Transfer Agent) that acts as a local relay.
In this post, we’ll look at the pros and cons of this setup and show how to integrate it seamlessly via `docker-compose.yml` and TYPO3.
The problem with synchronous SMTP in PHP
PHP applications operate synchronously by default. When a user submits a contact form in the frontend and TYPO3 is configured to connect directly to an external provider via SMTP, the following happens:
- PHP opens a socket connection to the external SMTP server.
- The TLS handshake is performed.
- The authentication data is exchanged.
- The email data is transmitted.
- The external server acknowledges receipt.
This entire process can easily take 1 to 3 seconds. We have customer servers that take up to 20 seconds to confirm that the email has been sent successfully. For the user, this means: the loading icon spins, and the page freezes. If the external SMTP server fails (timeout or rate limiting), in the worst-case scenario the PHP process aborts with an error, and the email is irretrievably lost.
The solution: concurrency via a local Postfix container
By integrating a lightweight Postfix container (such as the excellent image from boky/postfix) into our Docker stack, we completely decouple email delivery from PHP execution.
Advantages of the setup
- True concurrency (asynchronous sending): TYPO3 passes the email to the local Postfix container on port 25 in a matter of milliseconds. For PHP, the job is then done, and the frontend responds immediately. Postfix handles the forwarding to the actual SMTP provider in the background (asynchronously).
- Robust queue (spooling/queuing): If the external SMTP relay is temporarily unavailable or a rate limit is triggered, Postfix holds the emails in its own queue and periodically retries sending them. No emails are lost.
- Centralised authentication: The provider’s sensitive SMTP credentials are stored in a single location (within the Postfix container). The PHP application itself does not require any passwords, but trusts the local network.
- Security restrictions: Directives such as ALLOWED_SENDER_DOMAINS allow granular control over which sender domains the container is permitted to accept.
Disadvantages & challenges
- Additional container overhead: Another service that needs to be monitored, logged and maintained within the Docker stack. That said, we’re looking at memory usage in the low double-digit MB range here. The current example results in an overhead of 25MB in the server’s RAM.
- Security risk due to misconfiguration: If POSTFIX_mynetworks is defined too broadly, in the worst-case scenario you could end up with an open relay that attackers could exploit for spam. It must be strictly isolated to the internal Docker network.
- Ephemeral queue on container restarts: If there are emails in the Postfix queue and the container is force-stopped or deleted (without a persistent mounted volume for /var/spool/postfix), these emails will be lost.
Technical integration
1. Docker Compose configuration
Here is an example snippet for the docker-compose.yml file. We use the boky/postfix image, which can be configured very easily via environment variables:
services:
# ... TYPO3 / PHP Services, Datenbank, Cron, ...
postfix:
container_name: mysite_postfix
image: boky/postfix:latest
hostname: relay
logging:
options:
max-size: "2m"
max-file: "10"
environment:
- RELAYHOST=${SMTP_HOST}
- RELAYHOST_USERNAME=${SMTP_USER}
- RELAYHOST_PASSWORD=${SMTP_PASS}
- POSTFIX_mynetworks=127.0.0.0/8,10.10.0.0/16
- ALLOWED_SENDER_DOMAINS=${ALLOWED_SENDER_DOMAINS}
- POSTFIX_smtpd_tls_security_level=none
- POSTFIX_smtp_tls_security_level=may
The necessary data is then specified in the associated .env file
SMTP_HOST=[smtpserver.mydomain.de]:587
SMTP_USER=smtpusername
SMTP_PASS=sicheressmtppassword
ALLOWED_SENDER_DOMAINS=mydomain.de
Key details to note:
- POSTFIX_mynetworks: Allows connections from localhost and the internal Docker subnet (here, for example, 10.10.0.0/16 – this should be adapted to the actual Docker network).
- POSTFIX_smtpd_tls_security_level=none: As communication between the PHP container and Postfix takes place within the internal, isolated Docker network, no encryption is required here.
- POSTFIX_smtp_tls_security_level=may: Enables TLS for the outbound connection from Postfix to the external provider (RELAYHOST).
2. Configuration in TYPO3
As Postfix handles authentication with the final provider, the mail configuration in the TYPO3 installation tool (or in LocalConfiguration.php / AdditionalConfiguration.php) is reduced to a minimum. TYPO3 sends mail to the Postfix container unencrypted and without a login:
return [
'MAIL' => [
'defaultMailFromAddress' => 'admin@mydomain.de',
'defaultMailFromName' => 'RMS Admin',
'defaultMailReplyToAddress' => 'admin@mydomain.de',
'defaultMailReplyToName' => 'admin@mydomain.de',
'transport' => 'smtp',
'transport_smtp_domain' => 'mydomain.de',
'transport_smtp_encrypt' => false,
'transport_smtp_password' => '',
'transport_smtp_server' => 'postfix:25',
'transport_smtp_username' => '',
],
];
Conclusion
Outsourcing email dispatch to a dedicated Postfix relay container is a best-practice pattern for the production operation of TYPO3 and demanding PHP applications. It eliminates the dreaded loading time spikes when submitting forms and, thanks to the integrated queue, protects against data loss in the event of third-party service outages. The minimal extra effort involved in configuring Docker pays off immediately in the form of a more stable architecture and a noticeably better user experience.