noico.net

selfhosted email over onion (with smtdump, oniux, neomutt)

Introduction

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.

security

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.

installing smtpdump

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.

starting smtpdump

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.

fetching mail and setting up neomutt

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.

done?

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.