Setting Up Postal as an SMTP Server
Set up a secure and efficient SMTP server with Postal, from installation to advanced configuration.
For the past couple of weeks, I’ve been thinking about building my own mail server. While I have some knowledge of email servers, I had never set one up before.
I’ve always wanted to move away from third-party mail services to have more control over my emails. However, running a full mail server is a complex and troubleshooting-heavy task. Still, it’s worth the effort, as there’s so much to learn along the way.
I decided to take a more manageable approach: setting up my own SMTP server for sending emails rather than deploying a full-featured mail solution.
This gives me the flexibility to use my SMTP server for different purposes, such as sending bulk emails (like newsletters), transactional emails from my WordPress websites, or as an external relay for my email addresses (inboxes).
However, the most challenging part of this setup is ensuring that other mail servers trust yours so that your emails land in inboxes rather than spam, which is something I’ll cover in this guide.
I’ll focus on installing, configuring, and using Postal as an SMTP server for sending emails. Additionally, I’ll share valuable tips, tricks, and best practices for managing SMTP servers efficiently.
Get updates from my mail server journey – tips, lessons, and discoveries along the way.
Why Postal?
If you know me, you know that from my collection of server security guides, I’ve always tried to limit the use of third-party security software and prefer to build my own solutions.
I’ve always leaned towards the old school way of doing things, avoiding extra software and instead using the available server packages to suit my needs.
For example, I could have used Postfix as an SMTP server for outgoing emails, but I ultimately chose Postal.
The main reason I went with Postal is because it offers many features that Postfix lacks. More importantly, since this is a complex project, I didn’t want to complicate things even more by configuring everything manually.
Postal simplifies the process and offers an active community (via GitHub Discussions) that can help answer any questions or provide support when needed.
One key feature that made me choose Postal is its support for IP pools.
Postal allows you to configure multiple IP pools, meaning you can send emails from different IP addresses rather than being tied to the server’s main IP. This is incredibly useful for managing email reputation and separation.
For example, instead of using the same IP that Postal is installed on, I configure an IP pool and assign a different IP for sending. This keeps my main server IP separate from my sending IPs. I can even assign a dedicated IP for bulk emails and another one for personal or transactional emails.
This feature is a must-have in any SMTP server solution, as it gives you the flexibility to manage IPs based on different use cases. If you onboard a new client who needs an SMTP server but you don’t want them using your existing IPs, you can simply create a new IP pool and assign it to them.
But IP pools are just one of the many reasons to choose Postal.
Throughout this guide, you’ll discover more powerful features, such as its clean and intuitive web interface, its built-in integration with SpamAssassin to scan outgoing and incoming emails for spam, and even the ability to receive emails through routes (which I will cover as well).
Postal offers much more than just an SMTP server, and by the end of this guide, you’ll see why it’s a great choice.
Author's Note
Now that you know why I chose Postal as my SMTP server, I’d like to highlight some important points.
One of the most crucial factors in setting up an SMTP server is choosing a good server provider with a strong IP reputation. This applies not only to your main server but also to the IPs you use in your IP pools.
Your choice of IPs can make or break your email deliverability!
A poor IP reputation can cause your emails to be flagged as spam or even blocked entirely. That’s why it’s essential to carefully select a provider that offers clean, trusted IPs to ensure your emails reach inboxes rather than spam folders.
In my experience, Hetzner is the strictest server provider I’ve ever seen when it comes to sending emails.
Most providers block port 25 by default to combat spam, and while many will unblock it upon request, Hetzner is by far the most stringent in this regard. Their strict policies ensure that their IPs remain clean and trusted, making them an excellent choice for an SMTP server.
But even with a good IP reputation, you’ll still face challenges at the beginning. If your IP is new and hasn’t been used for sending emails before, many email servers will see it as suspicious. This means you’ll need to build trust over time before your emails consistently land in inboxes.
And something important as well: configuring IP pools requires the use of Floating IPs that Hetzner offers. These are public IP addresses that you can add to your Hetzner servers in addition to their primary IPs.
I’m not sure if other server providers offer this option, so make sure to check with your provider if you’re not using Hetzner.
Floating IPs remain yours even if you delete the server, which is why they’re great for creating flexible setups like IP pools. If something goes wrong and you need to configure a new server, you can assign the floating IPs to the new server without losing them. This way, you retain both the IPs and the reputation you've built over time.
Finally, make sure your provider has port 25 open for you. Without it, your SMTP server won’t be able to send emails.
Server Preparation
Before preparing our server to run Postal, we need to deploy it first.
Make sure to choose Ubuntu 24.04 LTS as the server's image, as it is a stable and reliable version for setting up your server.
Hetzner offers two types of servers: ones with shared resources and others with dedicated CPU cores. I highly recommend opting for the servers with dedicated CPUs, as an SMTP server is mission-critical and should never face resource limitations.
It is essential to always monitor your server's resources to ensure smooth operation. Additionally, make sure that IPv6 is enabled on your server, as this is important for modern email services.
After your server is deployed, ssh into it and begin by running the following commands to perform a full update:
apt update
apt dist-upgrade
Assuming you’re accessing the server as root for the first time, remember to change your root password to something stronger afterward.
Next, make sure to set the correct timezone for your server. For example, to set the timezone to Berlin, you can run:
timedatectl set-timezone Europe/Berlin
When configuring your hostname, note that a typical hostname consists of two parts: the server name and the domain name. Since we’re setting up an SMTP server, a recommended format for the hostname would be:
hostnamectl set-hostname mail.example.com
Using mail
as the server name is a good practice, as it clearly indicates to other mail servers that this server is handling SMTP functions.
Before proceeding, make sure to create a new user with sudo privileges and disable the root user for better security. Also, be sure to set up SSH keys for the new user and disable password authentication.
I’ve covered all of this in detail in my Server Preparation Guide, so feel free to check that out if you need help with these steps.
Now, there are a few things we need to do to prepare our server for installing Postal.
First, we need to install some dependencies. These are essential server utilities that Postal relies on to run certain commands:
sudo apt install git curl jq
This ensures that git
is available on your server, which we’ll use to clone the Postal installation helper repository:
sudo git clone https://github.com/postalserver/install /opt/postal/install
Then, create a symbolic link so you can run the postal
command from anywhere on the server:
sudo ln -s /opt/postal/install/bin/postal /usr/bin/postal
The postal
command needs to be run with sudo
privileges or as the root user.
Okay, now we need to install Docker and Docker Compose, because Postal runs entirely using containers:
sudo apt install docker.io docker-compose
Finally, we need to install MariaDB. Postal requires a database engine to store all emails and other essential configuration data, and it will automatically provision a database for each mail server you create.
We’ll run MariaDB in a container, which is the easiest option:
sudo docker run -d \
--name postal-mariadb \
-p 127.0.0.1:3306:3306 \
--restart always \
-e MARIADB_DATABASE=postal \
-e MARIADB_ROOT_PASSWORD=postal \
mariadb
This will start a MariaDB instance, listening on port 3306
. Make sure to change the root password to a strong one and store it securely, as you’ll need it later during the Postal configuration.
You can verify that MariaDB is running with:
sudo docker ps
This will show you the list of running Docker containers, and you should see the postal-mariadb
container listed with its status as Up
.
sudo reboot
your server to make sure all changes are fully applied.An important point to mention: If you go to the Primary IPs tab in Hetzner, you can enable protection for the primary IPs assigned to your server. These are the IPv4 and IPv6 IPs Hetzner provided when you deployed the server. I recommend enabling protection for these IPs as well.
This way, if something goes wrong and you need to configure a new server, you can reuse your primary IPs with the new server, retaining both the IPs and the reputation you've built over time – just like with Floating IPs.
Floating IPs Setup
Alright, now that our server is prepared for installing Postal, there’s still something we need to do – adding Floating IPs to our server.
In the menu on the left, you will find an item called Floating IPs. Click on it to access the Floating IPs tab.
Then, click the Add Floating IP button. Choose the same location you selected for your server. You should add two IPs – one IPv4 and one IPv6. Name them whatever you want.
You can add as many IPs as you want and create as many IP pools as you like, but make sure to note which IPv4 you want to use with which IPv6.
Now, from the three dots menu on the right next to the location of each IP, choose the Assign option to assign the IP to the server you deployed for Postal. Do the same for both IPv4 and IPv6.
Okay, the IPs are now assigned to the server, but they are not configured yet. We’ve assigned the IPs to the server at the network level, but we now need to tell the server to use them.
To do this, access the server and create a configuration file inside the /etc/netplan/
directory. You can do this by running:
sudo vim /etc/netplan/60-floating-ip.yaml
Now, add the following configuration to the file:
network:
version: 2
renderer: networkd
ethernets:
eth0:
addresses:
- 89.107.50.13/32
network:
version: 2
renderer: networkd
ethernets:
eth0:
addresses:
- 2a01:4f8:1c0c:80e9::1/64
For IPv4, leave the /32
at the end and only change the IP address before the slash to the one provided by Hetzner.
For IPv6, you will receive an IP in the following format:
2a01:4f8:1c0c:80e9::/64
Simply add the number 1
after the ::
to complete the address.
Once the configuration file is created, change its permissions to avoid receiving a warning when applying the changes:
sudo chmod 600 /etc/netplan/60-floating-ip.yaml
Finally, apply the changes with the following command:
sudo netplan apply
To verify the configuration, run the following command:
ip addr show
With this, you have successfully configured your server to use the new IPs.
You can add as many floating IPs as you want, assign as many as needed, and create a new configuration file for each IP pair.
Installing Postal
So far, we’ve prepared our server for Postal and configured the floating IPs we want to use as an IP pool.
Now it’s time to begin the Postal installation. I’ll divide the process into clear sections to make it easier to follow and understand.
Generating Configuration Files
Before starting the installation, Postal provides a tool that automatically generates the initial configuration files required for setup.
Run the following command, replacing mail.example.com
with the actual hostname you set earlier – the one you plan to use to access the Postal web interface:
sudo postal bootstrap mail.example.com
The command will generate three important files inside the /opt/postal/config/
directory:
postal.yml
– The main configuration file for Postal.signing.key
– A private key used by Postal to sign various internal components.Caddyfile
– The configuration file for the Caddy web server, which Postal uses to serve its web interface.
The only file that matters to us at this point is the postal.yml
file, which we’ll use to tweak and customize Postal’s configuration.
Once the files are generated, open the postal.yml
file and update the passwords for both the main_db
and message_db
sections. Use the password you specified earlier when creating the MariaDB instance with Docker:
main_db:
host: 127.0.0.1
username: root
password: <here_comes_the_password>
database: postal
message_db:
host: 127.0.0.1
username: root
password: <here_comes_the_password>
prefix: postal
Save and close the file – that’s all we need to do for now.
Note: Since the Docker setup mounts /opt/postal/config
as /config
, any file paths referenced inside postal.yml
should start with /config
instead of /opt/postal/config
. Keep this in mind when updating file paths in the configuration.
DNS Setup
For Postal to function properly, we need to configure several DNS records. This is one of the most critical steps in setting up any mail server – if your DNS isn’t configured correctly, your emails may never reach their destination.
A & AAAA
We’ll begin by setting up four essential records: two A and two AAAA records.
Record Type | Host | Value (IP Address) |
---|---|---|
A | mail.example.com | Primary IPv4 |
AAAA | mail.example.com | Primary IPv6 |
A | pool.example.com | Floating IPv4 |
AAAA | pool.example.com | Floating IPv6 |
- Replace
example.com
with your actual domain. - Replace
pool
with the name of the IP pool you want to create.
For each new floating IP pair you add, make sure to add a new A and AAAA record for them as well.
Lastly, we need to add A and AAAA records for the MX record that we will use later for receiving emails through routes.
Record Type | Host | Value (IP Address) |
---|---|---|
A | mx.mail.example.com | Primary IPv4 |
AAAA | mx.mail.example.com | Primary IPv6 |
Both records should point to the primary IPs of your server.
Reverse DNS (PTR)
After adding the A and AAAA records, the next step is to configure the Reverse DNS (PTR) records.
To do this, follow these steps:
- Access Hetzner's dashboard.
- Go to Server Details and navigate to the Networking tab.
- In the Networking tab, you’ll see two sections:
- PUBLIC NETWORK, which contains your primary IPs.
- FLOATING IPS, which contains any floating IP pairs you’ve assigned to the server.
- You’ll notice a REVERSE DNS entry next to each IP. This is where you need to make changes.
For each IP:
- Click the three dots menu on the right of the IP address and select Edit Reverse DNS.
- Change the reverse DNS entry to the hostname you chose for your primary and floating IPs.
For IPv6 addresses, append a 1
after ::
and set the hostname accordingly.
SPF
It's crucial to add an SPF (Sender Policy Framework) record to your DNS setup.
This record helps prevent your emails from being marked as spam by verifying that your server is authorized to send emails on behalf of your domain.
To do this, we add a global SPF record that includes our primary IPs, as well as the SPF record for each IP pool we create. Additionally, we will add individual SPF records for each specific IP pool.
Record Type | Host | Value |
---|---|---|
TXT | spf.mail.example.com | "v=spf1 ip4:70.130.219.212 ip6:643c:ac1f:3f7c:bb5c::1 include:spf.pool.example.com ~all" |
TXT | spf.pool.example.com | "v=spf1 ip4:89.107.50.13 ip6:2a01:4f8:1c0c:80e9::1 ~all" |
- The first record defines the global SPF record for our primary IPs and includes the SPF records for the IP pools.
- The second record is specific to the SPF record for the IP pool itself.
Return Path
The Return Path is the email address that gets used to handle bounces (rejected or undelivered emails) for any email you send.
It tells the receiving mail server where to send error messages if it can't deliver an email (like if the recipient's email address doesn't exist, or the mailbox is full).
Think of it like this:
- You send an email to someone.
- If the email can't be delivered, a bounce email will be sent back to the return path address, not your From address.
For this, we need to add three DNS records.
Record Type | Host | Mail Server | Priority |
---|---|---|---|
MX | rp.mail.example.com | mail.example.com | 10 |
- This tells the world where to send bounce emails for the return path.
- This means bounces for emails sent from
mail.example.com
will go torp.mail.example.com
(your return path domain), which will then point to your Postal server to handle them.
Record Type | Host | Value |
---|---|---|
TXT | rp.mail.example.com | "v=spf1 a mx include:spf.mail.example.com ~all" |
- This confirms that
mail.example.com
is allowed to send emails on behalf of your return path domain. - You need this SPF record to avoid spam filters rejecting your bounce emails.
The last DNS record we need to add is the DKIM record. DKIM (DomainKeys Identified Mail) is an email authentication method that uses a digital signature to verify that the email was sent and authorized by the domain owner.
It uses cryptographic signatures to verify that the message content has not been altered in transit, helping to prevent email spoofing and improving email deliverability.
Postal provides a command that generates a DKIM record for us, which begins with the postal
DKIM identifier. If you'd like to customize this identifier, open the /opt/postal/config/postal.yml
file and add the following to the dns:
section:
dns:
dkim_identifier: custom
Afterward, run this command to generate the DKIM record:
sudo postal default-dkim-record
In my case, I am adding a TXT record with the host value of:
custom._domainkey.rp.mail.example.com
The value for this record will be the output generated by the command.
Route Domain
If you wish to receive incoming emails by forwarding them directly to routes in Postal, you'll need to configure a domain and point it to your server using an MX record.
Record Type | Host | Mail Server | Priority |
---|---|---|---|
MX | routes.mail.example.com | mail.example.com | 10 |
- With this MX record in place, any incoming emails sent to
routes.mail.example.com
will be directed to your server and processed according to the routes you've configured in Postal.
This is essential for setting up email receiving capabilities and ensures that emails are properly routed to the right destinations.
DMARC
DMARC (Domain-based Message Authentication, Reporting, and Conformance) is an email authentication protocol that helps prevent email spoofing and phishing by allowing domain owners to specify how incoming emails should be handled if it fails SPF or DKIM checks.
The DMARC record specifies:
- Whether to accept, quarantine, or reject emails that fail authentication.
- How the domain owner wants to receive reports about these emails.
For this, add a TXT record with a host value starting with _dmarc
for your domain and containing the following:
"v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com"
p=quarantine
: This policy tells receiving mail servers to treat emails that fail DMARC checks as suspicious and place them in the recipient's spam or junk folder.rua=mailto:postmaster@example.com
: This specifies the email address where aggregate reports about DMARC failures will be sent, allowing the domain owner to monitor and analyze the performance of their DMARC policy.
Adding a DMARC record improves both your domain's security and email deliverability.
Verify Configuration
If you open the /opt/postal/config/postal.yml
file, you will see DNS records specified under the dns:
section.
This section essentially tells Postal which DNS records you’ve added and how Postal should recognize them.
By default, it contains all the DNS records we added, except for the Track Domain, which is used for click and open tracking and is out of the scope of this guide. You can simply comment it out.
Make sure all the records match the ones you’ve added, then save and close the file.
Final Touches
With this, we have successfully added all the required DNS records.
However, there are two additional DNS-related settings that you can customize inside the /opt/postal/config/postal.yml
file:
dns:
domain_verify_prefix: custom-verification
custom_return_path_prefix: customrp
By default, domain_verify_prefix
is set to postal-verification
and custom_return_path_prefix
to psrp
.
But, you can change these to match your domain or brand name for a more professional appearance.
Starting Postal
Finally, it's time to start Postal! But before we do that, there's one last thing to check – we need to enable the use of IP pools in the Postal configuration file.
Open the /opt/postal/config/postal.yml
file and make sure the following setting is included:
postal:
use_ip_pools: true
Once that's set, save and close the file.
Next, run these two commands to initialize the database and create your first admin user:
sudo postal initialize
sudo postal make-user
Perfect! You’re now ready to start Postal.
Run the following command to get everything up and running:
sudo postal start
This will launch all the necessary containers on your server. You can check their status at any time by running:
sudo postal status
If you run the sudo docker ps
command, you’ll notice several new containers up and running – these are the components that power your Postal installation.
Web Access with Caddy
To access Postal’s web interface, you’ll need to set up a web proxy.
You can use any reverse proxy you're comfortable with, but in this guide, we’ll use Caddy. It is a lightweight, modern web server that’s easy to configure and automatically handles SSL certificates via Let's Encrypt.
Run the following command to start the Caddy container:
sudo docker run -d \
--name postal-caddy \
--restart always \
--network host \
-v /opt/postal/config/Caddyfile:/etc/caddy/Caddyfile \
-v /opt/postal/caddy-data:/data \
caddy
Once it’s running, Caddy will automatically request and install an SSL certificate for your domain. You should then be able to access the Postal web interface using your hostname and log in with the admin user you created earlier.
Time to Mail It!
Now that Postal is installed and up and running, it's time to test our setup.
Start by creating your first organization, give it a name, and hit the Create Organization button.
Inside the organization, you'll find four different tabs. In the Settings tab, you can change the timezone to ensure the logs are synced with your local time.
Before adding your first mail server, there's something important to note: You have the option to add a domain either inside the organization (but outside a mail server) or within a specific mail server.
If you add a domain at the organization level, it will apply to all mail servers created within that organization. However, if you add a domain within a mail server, it will only apply to that specific mail server.
Personally, I’ve never added a domain outside a mail server, as DNS can get complicated and busy when the domain is linked to all mail servers created within the organization. This is something I was also advised by a member of the support team on GitHub.
And I love organizing things, so I add domains only inside mail servers for better separation.
However, if I ever find myself in a situation where I need to add a domain at the organization level, I’ll make sure to update this guide accordingly.
Alright, go ahead and create your first mail server now and make sure to set the MODE to Live.
Once inside the mail server, navigate to the Domains tab and add your first domain. You can add the same domain you're using for Postal itself. I always do this because I set up a route (for incoming mail) to postmaster@example.com
to receive DMARC reports, as mentioned earlier in the DNS Setup – DMARC section.
Also, there’s a setting worth noting: it’s called POSTMASTER and you’ll find it under the Settings tab, inside the Server Settings sub-tab. This is the email address other mail servers will be instructed to contact if one of their messages couldn’t be received by Postal.
For example, if someone tries to email an address you haven’t set a route for, it will bounce, and the bounce message will mention the POSTMASTER address as the contact point.
It’s set by default to postmaster@example.com
, and I recommend leaving it that way. Most mail servers will suggest contacting "postmaster" without specifying a full email address – they just assume it’s postmaster@example.com
. So it’s best to stick with the default.
If you're using another service to handle incoming mail and plan to use Postal only for sending, then you can skip adding the MX record Postal suggests when adding a new domain – it's optional and only needed for receiving emails through Postal.
If you're not using another service for incoming mail, I still recommend adding the MX record – it helps make your sending email address appear more trustworthy and legitimate.
Now it’s time to test your mail server. Head to the Messages tab, then open the Send Message sub-tab.
In the FROM field, use something like hi@example.com
– just make sure you've already added the domain you want to send from.
For the TO field, I recommend starting with this testing tool that checks how spammy your email appears to receiving servers. It's a great way to verify if everything is set up correctly.
After the tool generates a temporary email address, paste it into the TO field and click the Send Message button. Then head back to the tool and check your score.
I got a 10 out of 10, which is excellent! If your score is lower, the tool will break down exactly what went wrong so you can fix it.
Adding IP Pools
We’ve just added a new mail server and sent our first email – fantastic!
But so far, we've been using our primary IPs. Now it’s time to put our floating IP pair to use by adding them as an IP pool and testing how they perform.
From the top menu in Postal, click on IP Pools. This is where you can manage different sets of IPs for specific purposes.
Create your first pool and give it a descriptive name. For example, we can call it WP – meaning this pool will contain IPs used for sending emails from WordPress websites. Of course, you can name the pool anything you like and use it for any purpose.
Once the pool is created, you’ll see an empty table. Click the Add an IP address to pool button and fill in the required fields:
- IPv4 and IPv6 addresses (as a pair)
- Hostname (
pool.example.com
, or whatever you specified earlier in your DNS records)
Each IP set in a pool can have a priority between 1 and 100. This priority determines the likelihood that a specific IP will be chosen when sending an email. The higher the number, the more likely it is to be used.
This is especially useful when warming up new IPs. For example:
- IP A: priority 1
- IP B: priority 50
- IP C: priority 100
If you send 100,000 emails, IP A will send a very small percentage, IP B will send around one-third, and IP C will send the majority. This helps maintain sender reputation while gradually introducing new IPs.
By default, the priority is set to 100. Since you are adding only one IP set, you can leave it as is.
Now, go back to your organization, and you’ll notice a new tab called IPs. Open it and choose the new IP pool you just created. This will enable the organization to use the IP pool for sending across all mail servers created.
If you assign two IP pools, you can select which one to use from the Server Settings inside the mail server. Keep in mind that once you assign an IP pool to an organization, the mail servers you create will no longer use your primary IPs.
Okay, now go ahead and do the same as we did to test our primary IPs.
Feel free to share your results with me via email or in the discussion section at the end!
Configuring Routes
So far, we've focused on sending emails – now it's time to set up routes so you can receive incoming mail.
From inside your mail server, head to the Routing tab. You’ll see three different types of endpoints available: HTTP, SMTP, and Address.
The HTTP and SMTP endpoints are outside the scope of this guide for now, but feel free to explore them on your own and share what you discover in the discussion section at the end. I might update the guide with those once I’ve done some deeper testing.
For now, we’ll focus on the Address endpoint, which is the simplest option. It forwards incoming emails to an address you choose.
- Add an address endpoint and enter the email address you’d like incoming emails to be forwarded to (your personal or business inbox).
- After that, go to the Routes sub-tab and click Add your first route.
- In the Name field, enter the email you want to receive messages to. Assuming you added your Postal domain, you can put
postmaster
and select your domain – this is useful for receiving DMARC reports and bounce-related emails. - In the Endpoint field, choose the address endpoint you just created.
- Select your preferred Spam Mode (we’ll configure spam filtering later).
- Click the Create Route button.
- In the Name field, enter the email you want to receive messages to. Assuming you added your Postal domain, you can put
Try out your new route by sending an email to postmaster@example.com
and check if it lands in the inbox of the address you configured as the endpoint.
And that’s it – you’re now ready to receive incoming emails through Postal!
Once you receive a mail, you’ll see it listed under the Messages tab, inside the Incoming Messages sub-tab.
If you open a mail, you’ll first see a summary – like whether it's marked as spam.
Now, here’s something important to notice: If you go to the Outgoing Messages tab and open any mail you've sent, under the Properties tab, you’ll see info about how the message was delivered – like whether it was received over an SSL connection. This security detail is missing from incoming emails.
That means: our server is not receiving emails over a secure connection yet! 😬
Let’s fix that next.
SMTP TLS
By default, Postal does not have TLS enabled for incoming mail, which means your server isn’t currently receiving emails securely.
You can confirm this using the CheckTLS tool. Just enter your domain in the eMail Target field and click the Run Test button.
The test should return an error like:
TLS is not an option on this server
This tells us that it's time to configure Postal to support TLS for incoming mail.
Before we proceed, I want to clarify something important.
I personally spent three days battling with SMTP TLS, trying to figure out why it wasn’t working – only to discover that it was something really simple. The problem? The Postal documentation doesn’t explain this part well at all.
Here’s the deal: When sending emails, Postal doesn’t need an SSL certificate because the receiving server is the one that decides how the connection should happen. It’s the receiving server that offers an SSL certificate, not us. That’s why, in the Properties tab of any outgoing email, you’ll see that the email was received securely – even though Postal didn’t provide any SSL certificate.
Now when it comes to receiving emails, we are the receiving server – which means we need to advertise TLS and provide an SSL certificate if we want other servers to connect securely. If we don’t set that up, we can’t offer secure connections, and tools like CheckTLS will throw errors like the one you see now.
That’s why, in the Incoming Messages tab, you don’t see the same security indicators – because nothing secure is happening.
This only affects incoming emails – it does not impact your ability to send mail securely at all.
If you don’t plan to use Postal for incoming mail (or have another service handling that), you can leave it as-is. But I strongly recommend enabling TLS anyway – it takes just 5 minutes, and it won’t interfere with anything. Who knows? You might want to use routes later.
Use this command to confirm that no SSL is configured and TLS isn’t working:
openssl s_client -connect mail.example.com:25 -starttls smtp
This command connects to your mail server on port 25 and tries to upgrade the connection to a secure one using TLS. If TLS isn’t enabled yet, you’ll see an error like:
Didn't find STARTTLS in server response, trying anyway...
This output means your server isn’t advertising TLS as an option, so incoming connections can’t be encrypted – exactly what we’re trying to fix next.
We need an SSL certificate for both the hostname and the MX record. To obtain one, we’ll use Certbot. Start by installing it:
sudo apt install certbot
When issuing a certificate, Certbot needs to use port 80 for domain verification. However, that port is currently in use by the Caddy web server, which is handling web traffic for Postal's interface.
To work around this, we’ll temporarily stop the Caddy container, issue the certificate, and then start the container again – all in one command:
sudo docker stop postal-caddy && sudo certbot certonly --standalone -d mail.example.com -d mx.mail.example.com --agree-tos --email postmaster@example.com --non-interactive && sudo docker start postal-caddy
Once that completes, your certificate will be successfully issued.
You can check the certificate and obtain the certificate and key paths along with other relevant details by running:
sudo certbot certificates
Now that we have our SSL certificate, we need to copy it to the /opt/postal/config/
directory and rename its certificate and key files for Postal to use:
sudo cp /etc/letsencrypt/live/mail.example.com/fullchain.pem /opt/postal/config/smtp.crt
sudo cp /etc/letsencrypt/live/mail.example.com/privkey.pem /opt/postal/config/smtp.key
Next, change the file permissions for the certificate files:
sudo chmod 644 /opt/postal/config/smtp.*
Then, open the /opt/postal/config/postal.yml
file and add the following:
smtp_server:
tls_enabled: true
tls_certificate_path: /config/smtp.crt
tls_private_key_path: /config/smtp.key
Finally, restart Postal to apply the changes:
sudo postal restart
To verify that SMTP TLS is working, run this command again:
openssl s_client -connect mail.example.com:25 -starttls smtp
You should now see a successful TLS connection.
Next, run the CheckTLS test again by entering your domain as the email target and clicking Run Test. Check the results to confirm that TLS is properly enabled and functioning.
We’re not finished yet, haha!
Since Certbot can't issue an SSL certificate when Caddy is running, how will it renew our SSL certificate? That's what we’re going to fix now.
By default, Certbot automatically renews our certificate, but this won’t work because Caddy is running. If you're curious, the configuration for auto-renewal can be found inside the /etc/letsencrypt/renewal
directory.
Certbot handles auto-renewals using systemd timers instead of cron jobs. This timer run twice per day and can be checked using the following command:
sudo systemctl list-timers
You can examine the content of the timer with the following command:
sudo cat /lib/systemd/system/certbot.timer
Now, to solve this problem, we can use something called Renewal Hooks. Inside the /etc/letsencrypt/renewal-hooks
directory, you’ll find three different subdirectories:
deploy
: This directory is for scripts that run after a certificate is successfully renewed.post
: Scripts in this directory run after the certificate is renewed and deployed.pre
: These are scripts that run before Certbot tries to renew the certificate.
We use pre
to stop the Caddy container, deploy
to copy the renewed certificate files, change their permissions, and restart Postal, and post
for starting the Caddy container again.
Inside the pre
directory, create a script called stop_caddy.sh
with the following content:
#!/bin/bash
docker stop postal-caddy
Inside the deploy
directory, create a script called copy_ssl_cert.sh
with the following content:
#!/bin/bash
# Copy new certificate files
cp /etc/letsencrypt/live/mail.example.com/fullchain.pem /opt/postal/config/smtp.crt
cp /etc/letsencrypt/live/mail.example.com/privkey.pem /opt/postal/config/smtp.key
# Set correct permissions
chmod 644 /opt/postal/config/smtp.*
# Restart Postal
postal restart
Finally, inside the post
directory, create a script called start_caddy.sh
with the following content:
#!/bin/bash
docker start postal-caddy
Don't forget to give each script execute permissions but make sure to restrict them to root only.
You can test if everything is working by first checking the expiry date using the following command:
sudo certbot certificates
This should match the expiry date of the copied certificate. To check the expiry date of the certificate in Postal, run:
openssl x509 -enddate -noout -in /opt/postal/config/smtp.crt
Then, force the renewal using the following command:
sudo certbot renew --force-renewal
Lastly, check the expiry date again to make sure it matches. If it does, the setup is working correctly.
Installing SpamAssassin
For filtering spam emails, we will install and use SpamAssassin, a powerful and widely-used spam filtering tool.
By default, Postal will communicate with SpamAssassin's spamd
using a TCP socket connection (port 783).
You’ll need to install SpamAssassin on your server and enable it within Postal to begin filtering spam.
For installing SpamAssassin, use the following command:
sudo apt install spamassassin
Next, you will need to enable the SpamAssassin timer:
sudo systemctl enable --now spamassassin-maintenance.timer
It is used to update the spam rules automatically.
To enable spam checking, we need to add the following to the end of the /opt/postal/config/postal.yml
file:
spamd:
enabled: true
host: 127.0.0.1
port: 783
Finally, restart Postal to apply your changes:
sudo postal restart
By default, Postal doesn't filter outgoing emails, only incoming ones, after you install and enable SpamAssassin.
If you go to the Settings tab and Spam sub-tab inside the organization, you will see the settings for the spam threshold. I recommend leaving these settings as is since a threshold of 5 is a good starting point.
Remember when we configured our first route, there was a SPAM MODE setting. Now that spam checking is enabled, this SPAM MODE will be triggered if an incoming mail has a spam score above the threshold you set.
For example, if an incoming email has a spam score of 6, it will trigger the SPAM MODE you chose. If the spam score is below 5 (the default threshold), it will not trigger the SPAM MODE setting.
The Spam Failure Threshold is set to 20, meaning if an incoming mail has a score above 20, it will fail immediately.
Now, let's talk about what spam scores are before we enable spam checking for outgoing emails.
SpamAssassin works by analyzing an email and giving it a spam score.
- The lower the score, the better the chances of the email getting delivered successfully.
- Conversely, the higher the score, the higher the likelihood the message is labeled spam/junk.
- A score below 5 is considered decent, while anything above 5 indicates a higher chance of being filtered out.
SpamAssassin uses over 700 tests and a variety of analytical techniques to detect spam. Each attribute it checks contributes to the overall score.
Negative scores indicate the email is less likely to be spam, while positive scores suggest possible spam. A score of 0 is neutral, meaning the factor has little impact.
When setting up SpamAssassin, the score threshold can be adjusted, with 5 being the default.
To ensure successful email delivery, aim for a spam score below 5, with scores between 0-2 being optimal. Negative scores are even better, as they indicate the email is very unlikely to be marked as spam, though they can be difficult to achieve.
I recommend starting with a threshold of 5 and gradually lowering it based on your spam checking history. For instance, if you notice your outgoing emails consistently have a score of 3, consider lowering your threshold to 4.
Over time, work on improving your score and address the factors contributing to higher scores.
Okay, let's enable spam checking for outgoing emails.
Go to the Settings tab, then the Advanced Settings sub-tab, and enable spam checking by setting the OUTBOUND SPAM THRESHOLD to 5. After that, hit the Save Server button.
That's it!
System Email Configuration
To enable system-generated emails (such as password reset links) in Postal, you'll need to configure the smtp:
section in the Postal configuration file.
Open the /opt/postal/config/postal.yml
file and scroll to the bottom of the file, and you’ll find the smtp:
section. This is where you'll define the SMTP server Postal should use for sending system emails.
While you could use an external SMTP provider, since this setup is focused on self-hosting, we'll use our own Postal SMTP server.
Go into your mail server and click on the Credentials tab. Add a new credential, keeping the TYPE set to SMTP, and name it something like System
to keep things clear. Then click Create Credential – a password (or key) will be generated automatically.
Next, head over to the Help tab and select the Sending Email sub-tab. You’ll find all the information needed to complete the smtp:
section in your postal.yml
file.
Copy the provided username and password, and paste them into the SMTP section. The from_name
can be anything you like – it's just the name that will appear in the email. For the from_address
, it's a good idea to use something like postmaster@example.com
.
Make sure to also change the port from 2525
to 25
, since our SMTP server uses port 25 for sending emails.
Finally, restart Postal to apply your changes:
sudo postal restart
Now, try logging out of the Postal web interface and resetting your password. You should receive an email with a link to set a new one.
Enabling UFW Firewall
We finally want to enable the UFW firewall on our server, and I've left this to the end so you can check if everything still works perfectly after enabling it.
We only need to allow incoming connections, as outgoing traffic is allowed by default with UFW.
To configure this, we need to allow incoming traffic on the following ports: 22 (SSH), 25 (SMTP), 80 (HTTP), and 443 (HTTPS).
Use the following commands to allow these connections and enable the firewall:
sudo ufw limit 22/tcp
sudo ufw allow 25/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Sometimes, UFW may require a reboot to work correctly. So, if you encounter any issues, try rebooting the server.
Securing the Web Interface
Someone asked about having a kind of 2FA for the web interface, since Postal doesn't provide any 2FA feature for now, and brought up a really good point.
You can restrict access to ports 80 and 443 to a static IP (like a VPN), but this doesn't work for everyone.
So, to add an extra layer of security to the normal username and password authentication method that Postal provides, we can enable Basic Authentication through Caddy, which is easy to configure.
Inside the /opt/postal/config/
directory, you'll find the Caddyfile
file. Open it and add the following, so the configuration looks like this:
mail.examle.com {
reverse_proxy 127.0.0.1:5000
basicauth {
username $2a$14$dp6WI0ldDiY/lFUL5I7q7ug/29DbwHYe5oFO6Z6mN6SRDIz/1/lgK
}
}
Replace username
with the username you want to use, and what follows the username is the password, but hashed using bcrypt, which is what Caddy uses.
While the caddy hash-password
command works normally, it won’t work in this case since we run Caddy in a container, and the command is not available. As a quick alternative, you can use this online tool to hash your password. After generating the hash, replace the hash in the example above with your own, then save and close the file.
Now, run the following two commands to stop and start Caddy again:
sudo docker stop postal-caddy
sudo docker start postal-caddy
Finally, try to access the web interface, and you should not be able to do so unless you enter the username and password you just added to the Caddyfile
file.
I hope this helps solve the issue of not having 2FA. Thanks to Martin Kurz for raising this point!
Disaster Recovery
The final step in setting up your SMTP server is planning for disaster recovery. Think about what you’d do if something goes wrong – like a server breach, a misconfiguration, a bad update, or even something out of your control, like a fire in your provider’s data center. You need a way to quickly bring everything back online.
We already have our primary and floating IPs secured – they stay with us no matter what, which is great. Now, we need a reliable way to restore the server and assign those same IPs to it. The best way to do this is by using snapshots.
In Hetzner, you can go to your server’s Snapshots tab and create a snapshot, which is basically a copy of your server's disk at that moment.
If something happens, you can spin up a new server from that snapshot, assign the primary and floating IPs, and it should work immediately – no need to change any DNS settings.
Make sure to test this process a few times so you’re comfortable with it.
Snapshots are created manually. If you want automatic backups, you can enable Hetzner's backup feature. It costs 20% extra, but it keeps daily backups for a week. Once the week is over, old backups are replaced by new ones. You can convert any of these backups into a snapshot, which you can then use to restore your server.
Conclusion and Final Thoughts
Congratulations on reaching the end!
Remember to monitor your setup, fine-tune your spam settings, and keep your SSL certificate renewed.
And just as important – test your disaster recovery plan regularly to make sure everything still works as expected. It’s better to catch issues during a test than during a real outage.
If you run into any issues or need further help, feel free to revisit this guide or reach out for assistance. Happy emailing!
If you found value in this guide or have any questions or feedback, please don't hesitate to share your thoughts in the discussion section.
Discussion