In this guide, I would walk you through the steps of setting up an email server that can be used as a send-only mail server or just an outgoing only mail server, we would not be dealing with receiving mails, we only care about sending emails
There are a lot of use cases for this, from newsletters to personal email, to providing email service for your clients, and so on.
Before I get started, let me give you a bit of backstory of what led me to use Haraka:
I am working on my CMS (which is powering this website), and I constantly find it hard to integrate some form of email service into the project, and no, I don't want to use postfix, or any old program. I want something I can extend using a language I am familiar with, be easy to integrate into my CMS, and should be highly modular which led me to Haraka.
Haraka is a SMPT sever project writen in JavaScript (Node.js) which provides extremely high performance coupled with a flexible plugin system allowing Javascript programmers full access to change the behaviour of the server
Requirements To Follow The Guide:
- Basic JavaScript Knowledge
- Your Way Around The Terminal (Not Necessary as you can mimic the guide instruction)
- Debian-Based VPS Distro (Ubuntu, Devuan, POP OS, etc)
Getting Started (Installing Haraka)
Haraka won't work without gcc installed, install it like so:
apt install build-essential -y
Ensure you have the latest version of node and npm installed,
I am using Debian, so, I would be installing node and npm like so:
apt install curl -y && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - &&\
apt update && apt-get install -y nodejs
Once that is installed, install haraka globally (note that Haraka starts with Capital H and not h):
npm install -g Haraka
If you don't want to install haraka globally, remove -g
. if you don't know what that means, then use the above command as is.
If the command gives "nobody" errors run the next command
npm -g config set user root
npm install -g Haraka
Running Haraka
First, check if haraka is installed:
haraka -v
If you get: bash: haraka: command not found
run the following:
npm -g config set user root
npm install -g Haraka
Once haraka is installed, you need to create an instance or a copy of the haraka service config:
haraka -i devsrealm_email_service
The above would create the directory devsrealm_email_service with config and plugin directories within. It also sets the hostname used by Haraka to the output of the hostname.
The devsrealm_email_service contains the following:
- config: Directory containing various configuration files.
- docs: Directory containing documentation.
- package.json: An empty package.json, you could use this in case you want to install node dependencies
- plugins: Directory containing the custom plugins.
DNS Configuration
Note: you would replace website.com with the name of your website and 111.11.1.111 should be replaced with the IP of your mail server.
; an A record is declared to name your mail server
mail.website.com A 111.11.1.111
; an MX record is declared to let the world know that mail.website.com handles mail for website.com
website.com. MX 10 mail.website.com.
The above would be set in your DNS records. If you have a backup mail server should in case something goes wrong with your primary mail server, you can set it below the MX record, e.g:
; an A record is declared to name your mail server
mail.website.com A 111.11.1.111
; an MX record is declared to let the world know that mail.website.com handles mail for website.com
website.com. MX 10 mail.website.com.
website.com. MX 20 mail-backup.website.com.
Instead of setting website.com or mail.website.com, you can use @ and mail which is equivalent to website.com and mail.website.com, here is an example:
; an A record is declared to name your mail server
mail A 111.11.1.111
; add smtp if you want to make it fancy, this way you would be able to connect to your server with smtp.website.com
smtp A 111.11.1.111
; an MX record is declared to let the world know that mail.website.com handles mail for website.com
@ MX 10 mail.website.com.
@ MX 20 mail-backup.website.com.
Note: Since we are only using haraka for sending, the mx portion is useless since it is more or less used for handling incoming email for your domain, so, for the above, just do:
; an A record is declared to name your mail server
mail.website.com A 111.11.1.111
or
; an A record is declared to name your mail server
mail A 111.11.1.111
; add smtp if you want to make it fancy, this way you would be able to connect to your server with smtp.website.com
smtp A 111.11.1.111
Once you have added the DNS records, you can confirm:
$ host mail.website.com
mail.website.com has address 111.11.1.111
$ dig -t MX website.com +short
10 mail.website.com.
Still, in your DNS records, add the below:
website.com. IN TXT "v=spf1 mx -all"
SPF makes it possible for destination mail servers to determine if a machine is allowed to send mail on behalf of a domain.
The above SPF is saying only the mail server is allowed to send mail on behalf of the domain.
Lastly, set your rDNS in your VPS control panel to mail.website.com, if you do not know how to do it, please ask your VPS customer agent.
Generate a Letencrypt Certificate
Install certbot:
apt-get -y install certbot
then generate cert-only this way:
certbot certonly --standalone -d "mail.website.com" -m "mail@website.com" --agree-tos --hsts --staple-ocsp --non-interactive
Once that is generated, it would be saved at:
/etc/letsencrypt/live/website.com/fullchain.pem
and the private key would be at
/etc/letsencrypt/live/website.com/privkey.pem
Setting Up Haraka
tls
open the config/tls.ini
nano /path/to/haraka/config/tls.ini
and refer to the new certificate:
key=/etc/letsencrypt/live/website.com/privkey.pem
cert=/etc/letsencrypt/live/website.com/fullchain.pem
me
Here you can set the name to use of the server, it would be used in received lines and elsewhere, by default it is set to your server hostname,
open config/me, and add mail.website.com
on the first line.
host_list
Here, you add the host you want to accept mail for, this is not important for a send-only mail server as it is used when you want to receive an email, so you can skip this.
To accept emails for your domain, you need to add them to the config/host_list file. for example, to accept emails for website.com (i.e., mail@website.com, user@website.com) you need to add website.com to the host_list file as a single line.
In my case, I added the following:
localhost
mail.webiste.com
webiste.com
Set to accept mail for localhost, mail.website.com, and website.com, again, this option can be skipped since we are only using haraka as a send-only, I just wanted to point it out
SMTP
Open config/smtp.ini, uncomment listen, and use it as follows:
listen=111.11.1.111:587
Meaning we are listening on port 587, you can as well change it to 465, but I would stick with 587
uncomment spool_dir, it is like this initially:
;spool_dir=/var/spool/haraka
change it to
spool_dir=/var/spool/haraka
close the file
auth_enc_file
When connecting to the SMTP server, we need a way to authenticate with our user and pass, by default, haraka has a flat_file which stores password in clear text, so we would not use it, instead, install auth_enc_file
using the following:
npm install haraka-plugin-auth-enc-file
once that is installed, open the config/auth_enc_file.ini file using:
nano config/auth_enc_file.ini
and use the following format, you can have multiple user:pass, just make sure they are on a separate line:
mail={SHA512-CRYPT}xxxxxx
otherUser={SHA512-CRYPT}xxxxxx
You would replace xxxxxx with a SHA512-CRYPT hash, let's say you want your password to be, my_mum_is_lovely, you would generate it this way:
mkpasswd -m sha-512 -s my_mum_is_lovely
which gives the output
$6$LP5cw5oGVvl4xFPI$ZOC4hSFVpCT/r5hwACfyeh5ENU53zVGoht6cx/4f5Pzr.NABk1jipJRsDan5hd8NtwBJf38vVpt49ZhHJrQP71
Now, you can just change the xxxxxx to the above, and repeat the step for another user in the auth_enc_file.ini.
With that, it means you would be able to connect your SMTP server with the following settings:
For the mail user:
- Host: mail.website.com
- User: mail
- Pass: my_mum_is_lovely
- Port: 587
- Encryption: tls
For the other user:
- Host: mail.website.com
- User: otherUser
- Pass: your_choosen_pass
- Port: 587
- Encryption: tls
You won't be able to send anything yet as we are still setting up haraka
dkim
DomainKeys Identified Mail allows the sending server to use a cryptographic signature, storing the public decryption key in a DNS record. The receiver can then verify the signing server has a key for that domain.
cd into the dkim directory:
cd /path/to/haraka/dkim
chmod 744 dkim_key_gen.sh
generate a key for website.com
./dkim_key_gen.sh website.com
When that is done, haraka would have created 4 files in config/dkim/website.com:
dns private public selector
The selector file contains the DNS label where the DKIM public key is published. The private and public files contain the DKIM keys.
The DNS file contains a formatted record of the public key suitable for copying/pasting into your domain's zone file. It also has suggestions for DKIM, SPF, and DMARC policy records.
use cat to view the DNS (I am only highlighting the important bit):
Add this TXT record to the website.com DNS zone.
....
....
BIND zone file formatted:
feb2023._domainkey IN TXT (
"v=DKIM1;p=MIIBIjANBgkxxxxxxxxxxxxxxxxxxxxxxxxxxxuHDDuMexuecP6JiHVzdZppTmANfB1Ak91s0Y1Ev+OW"
"k/QMsrJ+bCeTOIocXXX6+WqZedddddddddddddddddddSk/e5zjR7ikXRbnsdx2sf5YmtJjv6yDpe1PUCnHR4zjryr2U"
"FVp69FbiZMDM9PT0lWedfAM3wV+t3Udddddddddddd9ihpMnSLXX6vnCwe349VKQqrt+BS/hwZZhyLWfYdx"
"0mtOQdDAQAB")
The instruction on how to add it to the txt record would be there. Once you are done with that, you can also add a dmarc:
_dmarc IN TXT "v=DMARC1; p=reject; adkim=s; aspf=r; rua=mailto:dmarc-feedback@website.com; ruf=mailto:dmarc-feedback@website.com; pct=100"
Now, open /path/to/haraka_instance/config/dkim_sign.ini and add the following:
headers_to_sign=Subject,From,To
Enable plugins
Open /path/to/haraka_instance/config/plugins and uncomment (by uncomment I mean, remove the pre-pended # symbol) the following plugin:
- tls
- spf
- dkim_sign
- haraka-plugin-auth-enc-file
If haraka-plugin-auth-enc-file is not in that file, just add it at the very last line, then close.
Manage Haraka With SystemD
This way you would be able to start haraka automatically on boot, disable, and easily manage haraka instances
Create a new service file for Haraka. You can create it in the /etc/systemd/system directory with the following command:
sudo nano /etc/systemd/system/haraka_instance_1.service
add the below:
[Unit]
Description=Haraka Mail Server
After=network.target
[Service]
ExecStart=/usr/bin/haraka -c .
WorkingDirectory=/path/to/haraka_instance_1
Restart=always
RestartSec=10
User=root
[Install]
WantedBy=multi-user.target
Save the File
Reload the Systemd daemon to make the changes effective:
sudo systemctl daemon-reload
Start the Haraka service using the following command:
sudo systemctl start haraka_instance_1
Enable the new haraka service using the following command:
sudo systemctl enable haraka_instance_1
To check the status of the Haraka service, run:
sudo systemctl status haraka_instance_1
Haraka full logs would be logged at Syslog
Testing Smtp Server
If everything went well, you can test your SMTP server, for example, for the mail user, you can use the following details:
For the mail user:
- Host: mail.website.com
- User: mail
- Pass: my_mum_is_lovely
- Port: 587
- Encryption: tls
Good luck.
If you get stuck or you want to hire me for consultation, then reach out to me: faruq [at] devsrealm.com
Troubleshooting
Here are some common issue and their fixes
Couldn't reach server. Please double-check the server and port number
The "Couldn't reach server. Please double-check the server and port number
" error is issued by Gmail when using the "Send mail as
" feature. The error is because Google could not reach the server, and the likely culprit is a malformed or incorrect DNS setting, check the following:
- Your mail hostname is pointing to the IP of the server hosting the domain
- If you are using AAAA records, cross-check that you are using the correct IPV6, Gmail would try to connect to IPV6 and if it fails, it halts, you'll think it would try to also reach the IP of the A record, but that isn't the case, so ensure the IPV6 is correct, or just delete it altogether, this way, Gmail would use the A record IP