Understanding the Architecture
To receive emails with a custom domain, your DNS must point the MX record to your server’s public IP address. When an external mail server attempts to deliver mail, it connects to your server on port 25 (or 587) using the Simple Mail Transfer Protocol (SMTP). Postfix acts as the recipient agent, validating the domain and deciding where to place the message. For integration with a Node.js application, the most reliable method is not to expose a raw socket directly to Postfix, but rather to use Postfix’s local delivery agent (LDA) or a relay transport to pass the email content to your script.
A frequent mistake in this setup involves misconfiguring the network interfaces. If you set inet_interfaces = loopback-only, Postfix will only listen on 127.0.0.1. This means external clients cannot connect, resulting in "Connection Refused" errors. Conversely, if you simply bind to all interfaces without proper restrictions, you risk becoming an open relay. The correct approach is to bind to the specific public IP address of your server or to all while restricting access via firewall rules and Postfix’s own access controls.
Configuring Postfix for Your Custom Domain
The core of receiving mail lies in telling Postfix that your custom domain is one it should accept. This is managed through the mydestination parameter in /etc/postfix/main.cf. However, there is a critical distinction between domains you host locally and domains you wish to process via an external agent like Node.js.
If you add your custom domain to mydestination, Postfix treats it as a local mailbox. It will attempt to deliver the mail to a system user or a virtual mailbox file. While this works for traditional email clients, it complicates integration with Node.js because you would need to parse mbox files or use a separate LDA. A cleaner architecture often involves treating the domain as a relayed destination or using virtual transports. However, for many simple Node.js setups, keeping the domain in mydestination and using a local delivery agent that pipes to stdin is the most straightforward path. Ensure that mydestination includes your hostname, localhost, and your custom domain:
mydestination = $myhostname, localhost.$mydomain, localhost, example.com
Replace example.com with your actual custom domain. After making changes, restart the service with sudo systemctl restart postfix.
Resolving Local Delivery Conflicts
You may encounter issues where Postfix tries to deliver mail externally despite your intentions, or vice versa. If you are seeing logs indicating "Local Delivery despite External Agent Configuration," check your transport_maps. These maps override default routing behavior. If you have defined a transport for your domain that points to a relayhost, Postfix will send the mail out instead of processing it locally. Remove any conflicting entries in /etc/postfix/transport for your receiving domain if you intend to handle them locally via Node.js.
For more details on troubleshooting these delivery conflicts, see Troubleshooting Postfix: Local Delivery despite External Agent Configuration.
Integrating Node.js with Postfix
Once Postfix accepts the mail, you need a mechanism to hand it off to your Node.js application. The standard Linux way to do this is by configuring a local delivery agent (LDA) such as procmail or a custom pipe in the virtual_alias_maps or mailbox_command. A robust method is to use a virtual alias that pipes the message to a script.
Create a script, for example /usr/local/bin/node-email-handler.sh, which executes your Node.js script. The script should read from standard input (stdin), which contains the raw email headers and body. Here is a basic structure for the shell wrapper:
#!/bin/bash
/usr/bin/node /path/to/your/email-handler.js < /dev/stdin
Ensure the script is executable with chmod +x /usr/local/bin/node-email-handler.sh. Then, configure Postfix to use this handler. You can define a virtual map in /etc/postfix/virtual:
@example.com pipe:nodehandler
And in main.cf:
virtual_alias_maps = hash:/etc/postfix/virtual
virtual_transport = local
After updating the virtual map, run sudo postmap /etc/postfix/virtual and restart Postfix. Now, when mail arrives for @example.com, Postfix will pipe it to your Node.js script.
Handling Dynamic IPs and Security
If your server uses a dynamic IP address, receiving mail can be challenging due to spam filters and blacklists. Many receiving servers reject mail from dynamic IPs because they are commonly associated with spam. To mitigate this, ensure your reverse DNS (PTR) record matches your hostname. Additionally, consider using a dedicated static IP for email services if possible.
For guidance on managing email delivery issues related to IP addresses, refer to Handling Email Delivery with Dynamic IP Addresses on Ubuntu Server using Postfix.
Security is also paramount. Do not leave Postfix open to the internet without authentication for sending. Since we are focusing on receiving, ensure that your Node.js application validates the sender and handles the email content securely to prevent injection attacks. Use TLS certificates to encrypt incoming connections if supported by your setup, adding smtpd_tls_security_level = may to main.cf.
Frequently Asked Questions
Why does my Node.js script not receive the full email?
Ensure that your shell wrapper correctly passes standard input to the Node.js process. If the script hangs, it might be waiting for EOF. Verify that the piping mechanism in Postfix is functioning by testing with sendmail locally first. Check the system logs at /var/log/mail.log for any errors during the delivery phase.
Can I use Port 587 for receiving emails?
Port 587 is primarily intended for mail submission from authenticated users (clients sending mail), not for receiving mail from other servers. Inter-server communication typically occurs on Port 25. Configure Postfix to listen on Port 25 for inbound mail and reserve Port 587 for outbound submission if needed.
How do I debug why Postfix rejects my domain?
Check the mydestination setting in main.cf to ensure your custom domain is listed. Also, verify that your DNS MX records are correctly pointing to your server’s IP. Use postconf -n to review your current configuration and look for syntax errors.
What if I want to store emails in a database instead of parsing them?
Your Node.js application can connect to a database upon startup. As the email is piped in via stdin, parse the MIME structure using a library like nodemailer’s built-in parser or mailparser. Insert the parsed data into your database within the script. This allows for scalable storage and retrieval.
Does changing inet_interfaces affect security?
Yes. Setting inet_interfaces = all exposes Postfix to all network interfaces. If your server has multiple IPs, ensure that only the intended public IP is accessible from the internet. Use firewall rules (like UFW or iptables) to restrict access to Port 25 and 587 to trusted networks or the entire internet as appropriate for your setup.