it's an enigma to me that basically no one out there on the internet bothered to make a simple, working way to self-host an email server that runs only inside the onion network. email itself is a simple protocol (like the name says - rfc821).
+----------+ +----------+
+------+ | | | |
| User |<-->| | SMTP | |
+------+ | Sender- |Commands/Replies| Receiver-|
+------+ | SMTP |<-------------->| SMTP | +------+
| File |<-->| | and Mail | |<-->| File |
|System| | | | | |System|
+------+ +----------+ +----------+ +------+
Sender-SMTP Receiver-SMTP
Model for SMTP Use
the so-called usage philosophy was mostly based on trust (just like we trust people not to send us a bomb in a package).
somehow people managed just fine without SPF, DKIM, DMARC, DNSBL, RBL, greylisting, HELO/EHLO validation, reverse DNS check, SASL, TLSA, DANE, MTA-STS, ARC, rate limiting, tarpitting, spamtraps, fail2ban, SPF macros, DNSSEC, IP reputation, content filtering, header anomaly detection, envelope sender verification, bounce address tag validation, BATV.
unfortunately, as more people got online, abuse and spam started showing up, so some kind of protection and restrictions became necessary. at first, security improvements were driven by the community - but over time, control shifted to corporations. they took over, offering free email accounts and slowly locking most users inside their closed ecosystems.
simple mail protocol stopped being simple - it turned into corporate mail protocol. the entry barrier for running your own mail service is insanely high now. mail servers need complex setup, and even with good intentions and a high skillset, your IP or domain might still get blacklisted or greylisted. you also have to stay constantly up to date - an email server is not something you can just set and forget.
in this text, i want to show an alternative - a self-hosted smtp server that works only inside the onion network.
my setup is more like a workaround (some would call it a duct-tape solution).
basically, you run smtpdump on your own vps or computer - it’s a kind of fake smtp server that just accepts messages and doesn’t relay them anywhere.
so how do we enable smtp server <-> smtp server communication, like described in the original rfc?
super simple (even though we’re actually skipping the whole smtp-to-smtp communication part):
when sending an email to anon@server123456789.onion, you don’t send it through your own smtp server - you send it directly to the recipient’s server: server123456789.onion.
you can fetch messages to your local machine using rsync or scp.
or just read them remotely over ssh.
before messing with your smtp server, you really need to lock it down a bit.
smtpdump doesn’t support any auth out of the box, so literally anyone can spam it endlessly.
if someone feels like it, they can flood your inbox with a million messages and basically take out your vps or box.
easiest fix? a script that runs from cron every minute:
* * * * * /root/mail/mailguardian
it will shut down the server if there are more than 100 messages in inbox or if the total size goes over 300 mb.
#!/bin/bash
# path to the inbox dir
inbox_dir="/root/mail/inbox"
# count how many files are in the inbox
file_count=$(find "$inbox_dir" -type f | wc -l)
# check the total size of inbox in megabytes
dir_size_mb=$(du -sm "$inbox_dir" | cut -f1)
# if more than 100 files or size over 300 MB, take action
if [ "$file_count" -gt 100 ] || [ "$dir_size_mb" -gt 300 ]; then
# kill all smtpdump processes
killall smtpdump
# send alert to ntfy
curl -X POST "https://ntfy.sh/ourserver" -d "server under attack!"
fi
how the ntfy notification system works - check out https://docs.ntfy.sh/ (yeah, you can totally self-host it)
those are the only attack vectors i can think of right now. wouldn’t be surprised if there are more clever ones out there.
you’ll need: golang, git, openssl, tor, nohup. i
assume you’re root on your server.
cd ~ git clone https://github.com/awoodbeck/smtpdump.git cd smtpdump go build cd .. mv smtpdump mail cd mail openssl req -nodes -new -x509 -keyout key.pem -out cert.pem enter,enter,enter...
Now, we have to edit /etc/tor/torrc. Add there:
HiddenServiceDir /var/lib/tor/mail/ HiddenServicePort 25 127.0.0.1:25
and restart tor (assuming you're using systemd)
systemctl restart tor
check your new generated hostname by
cat /var/lib/tor/mail/hostname
and store somewhere its output.
cd /root/mail mkdir -p inbox nohup ./smtpdump -addr 127.0.0.1:25 -debug -cert cert.pem -key key.pem -tls13 -output /root/mail/inbox > output.log 2>&1 &
Let’s check if everything is ok
cat nohup.out
you should see:
2025/08/03 08:17:11 Enabled TLS support 2025/08/03 08:17:11 Minimum TLSv1.3 accepted 2025/08/03 08:17:11 Listening on "127.0.0.1:25" ...
our server is up and ready to receive messages.
to handle mail you’ll need two things:
a script that fetches messages and neomutt run via oniux.
i’m assuming you already have both tools installed.
first, let’s set up a directory for storing mails and the neomutt config:
mkdir -p ~/mail/{drafts,inbox,sent,trash}/{cur,new,tmp} && [ -f ~/mail/neomutt.config ] || touch ~/mail/neomutt.config
a basic fetch script could look like this:
#!/bin/bash # remote server info remote_user="root" remote_host="1.2.3.4" remote_port="22" # remote path remote_path="/root/mail/inbox/" # local path local_path="$HOME/mail/inbox/new" # create local dir if it doesn't exist mkdir -p "$local_path" # sync files from remote and delete them from source after # this avoids ownership/permission problems by not preserving them rsync -rltv -e "ssh -p $remote_port" --remove-source-files "$remote_user@$remote_host:$remote_path" "$local_path"
you can add it to your cronjobs or just run it manually whenever you want.
Basic neomutt configuration:
#set smtp_url = "smtp://frank1111989898789anqnguxb6mye6ypmgjmepouocmf45qzt5zqd.onion:25" #set smtp_pass = "" send-hook '~t frank' 'set smtp_url="smtp://frank1111989898789anqnguxb6mye6ypmgjmepouocmf45qzt5zqd.onion:25" smtp_pass=""' set mbox_type=Maildir set folder=~/mail/ set spoolfile=+inbox set record=+sent set postponed=+drafts set trash=+trash set mail_check=5 mailboxes +inbox +sent +trash +drafts set from = anon set hostname = anon set ssl_verify_host = no set ssl_verify_dates = no #other alternative_order text/plain text/html auto_view text/html set mark_old = no set sort=threads set sort_aux=reverse-last-date-received set strict_threads=yes set uncollapse_jump=yes set sort_re set indent_string=" " # Sidebar mappings set sidebar_visible = yes set sidebar_width = 23 set sidebar_short_path = yes set sidebar_format = "%B %* [%?N?%N / ?%S]" set sidebar_next_new_wrap = yes set mail_check_stats bind index,pager \Ck sidebar-prev bind index,pager \Cj sidebar-next bind index,pager \Co sidebar-open bind index,pager \Cp sidebar-prev-new bind index,pager \Cn sidebar-next-new bind index,pager B sidebar-toggle-visible
the most important line in this config is this one:
send-hook '~t frank' 'set smtp_url="smtp://frank1111989898789anqnguxb6mye6ypmgjmepouocmf45qzt5zqd.onion:25" smtp_pass=""'
here’s how it works: if you type frank in the “To:” field, neomutt will automatically set the smtp address to frank1111….onion.
for each person you’re talking to, you need to define this kind of “map” manually.
not the most convenient setup - but if you hook in an external smtp-sending program, you can automate it more. for now, we’re keeping it dead simple.
if you want someone to be able to send you messages, you just need to share your smtp server address with them
(the output of cat /var/lib/tor/mail/hostname).
it doesn’t matter what he put in the "To:" header - what matters is that they use the recipient’s smtp server.
that’s it. launch neomutt like this:
oniux neomutt
keep in mind: the sender can mess with the message headers however they want - so besides securing your smtp server, it’s a good idea to use gpg keys for extra trust.