Futuristic SMTP INBOUND-only server for home usage.
Find a file
LowEel 3a1550815b
All checks were successful
continuous-integration/drone Build is passing
New test
2026-05-12 20:49:44 +02:00
.vscode Some improvement 2026-05-10 14:45:42 +02:00
imapd Fix IMAP COPY of flagged Maildir messages 2026-05-12 11:26:46 +02:00
smoketest New test 2026-05-12 20:49:44 +02:00
smtpd Better tls logging 2026-05-12 11:44:18 +02:00
vendor Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
webcal Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
.drone.yml Modernizing 2026-05-10 15:01:28 +02:00
.gitignore Ignore Python cache files 2026-05-12 10:28:20 +02:00
backend.go Refactoring + IMAP 2026-05-10 14:23:06 +02:00
config.go Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
docker-compose.example SMTP ENV rational 2026-05-10 16:22:08 +02:00
Dockerfile Modernizing 2026-05-10 15:01:28 +02:00
example-run.sh No maildir creation. 2026-05-10 15:55:02 +02:00
go.mod Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
go.sum Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
graylist.go Fmt... 2026-05-12 09:00:58 +02:00
handler.go Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
LICENSE Update 'LICENSE' 2021-02-02 19:09:02 +00:00
logger.go Refactoring 2026-05-10 13:39:38 +02:00
panic_recover.go Harden IMAP handling and add server panic recovery 2026-05-12 11:16:30 +02:00
README.md New test 2026-05-12 20:49:44 +02:00
REFACTORING.md Client compatibility 2026-05-11 07:42:22 +02:00
run.sh Some improvement 2026-05-10 14:45:42 +02:00
session.go Fmt... 2026-05-12 09:00:58 +02:00
SINGLE_DOMAIN_GUIDE.md Refactoring + IMAP 2026-05-10 14:23:06 +02:00
smoketest.py Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
tls.go Fmt... 2026-05-12 09:00:58 +02:00
version.go Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00
zangtumb.go Add mail-driven WebCal feed 2026-05-12 20:43:55 +02:00

ZangTumb

Futuristic SMTP INBOUND-only server for home usage.
Futuristic SMTP/IMAP Catch-All server for home usage.
Inspired by Marinetti's RFC (also known as the Manifesto of Futurism).

It only serves a specific list of email addresses. No aliases.
It serves a single domain in catch-all mode: one mailbox for everything.

Everything else will apparently be accepted and then silently discarded, so that spammers waste their time (and money).
Every message sent to your domain will be accepted and stored in a single Maildir.
Spammers waste resources; you receive every message.

REQUIREMENTS

  1. Golang version >= 1.13
  2. Golang version >= 1.20
  3. git

(1 and 2 implies arithmetic, which then implies Gödel. So yes, we need Gödel too. Just kidding, it was a mistake, so I made it fun)

INSTALLATION

First, download the code into the folder you want to use with Golang:

git clone https://git.keinpfusch.net/loweel/zangtumb.git
go build -mod=vendor

./zangtumb

Set the environment variables (see below) and run ./zangtumb to start the daemon.

CONFIGURATION

ZangTumb is configured exclusively via environment variables.
It is designed to be easily containerized.

A reference pseudo-Dockerfile could be:

FROM debian:stable-slim  

## MAIN
ENV DOMAINNAME "yourdomain.tld"
ENV MAILUSER "admin"
ENV MAILPASSWORD "a-secure-password"

## SESSION
ENV MAILFOLDER "/zangmail"
ENV SMTP_LISTEN ":25"
ENV IMAP_LISTEN ":143"

## CERTIFICATES (Optional, auto-generated if missing)
ENV KEYFILE "/certs/mydomain.key"
ENV CERTFILE "/certs/mydomain.crt"

RUN useradd -ms /bin/bash zangtumb 
RUN mkdir -p /zangmail
COPY . /opt/zangtumb/

RUN chown -R zangtumb:zangtumb /opt/zangtumb
RUN chown -R zangtumb:zangtumb /zangmail

EXPOSE 25 143

USER zangtumb
WORKDIR /opt/zangtumb
ENTRYPOINT ["/opt/zangtumb/zangtumb"]

Everything is configured using environment variables, as follows:

ENV STRING Example Value Meaning
DOMAINNAME "mydomain.tld" The domain for which mail will be received.
MAILUSER "admin" The single catch-all user. All mail for *@domain will end up here.
MAILPASSWORD "secret" Password used to access the Maildir over IMAP.
SMTP_LISTEN ":25" SMTP listening port. TLS is mandatory.
IMAP_LISTEN ":143" IMAP listening port used to read mail.
MAILFOLDER "/zangmail" Root storage path. Maildir will be created in /zangmail/admin/Maildir/.
KEYFILE "/certs/key.pem" TLS private key path. Automatically generated if missing.
CERTFILE "/certs/cert.pem" TLS certificate path. Automatically generated if missing.
GRAYLISTING_ENABLED "true" Enable graylisting protection.
GRAYLISTING_DB_PATH "/tmp/gray.db" Path for the SQLite database. Must be on local storage, not NFS.
GRAYLISTING_DELAY "30s" Time the sender must wait before the mail is accepted.
GRAYLISTING_EXPIRY "36h" Lifetime of a graylisting record.
WEBCAL_ENABLED "true" Enable the optional mail-driven read-only iCalendar feed.
WEBCAL_INGEST_USER "secretary" Local part of the calendar ingest address, e.g. secretary@domain.
WEBCAL_LISTEN ":8088" HTTP listening address for the authenticated read-only calendar feed.
DEBUG "true" Enable verbose logging for SMTP and IMAP protocols.
MAX_MESSAGE_SIZE "10451212" Size, in BYTES, of a single email body

Of course, if you place your certificates into /certs (as in the example), that folder must already exist.

ZangTumb will automatically create the Maildir structure on first startup.

That's it.

WEBCAL: MAIL-DRIVEN READ-ONLY CALENDAR

ZangTumb can optionally expose a read-only iCalendar feed generated from calendar invitations received by email.

This feature is deliberately small.

It is not CalDAV.
It is not WebDAV.
It does not allow clients to edit the calendar through HTTP.

It only does this:

calendar invitation email
        ↓
special calendar ingest address
        ↓
ZangTumb extracts the iCalendar payload
        ↓
ZangTumb merges the event into one calendar file
        ↓
clients download that file over authenticated HTTP

The generated file lives here:

MAILFOLDER/webcal/calendar.ics

and is served over HTTP at:

/calendar.ics

Access to the HTTP endpoint uses the same credentials as IMAP:

username: MAILUSER
password: MAILPASSWORD

Conceptual model

WebCal works by using a special mailbox as a passive calendar attendee.

For example, with:

DOMAINNAME=keinpfusch.net
WEBCAL_INGEST_USER=secretary

the WebCal ingest address becomes:

secretary@keinpfusch.net

When that address receives an iCalendar message, ZangTumb imports the event into:

MAILFOLDER/webcal/calendar.ics

Then any calendar client subscribed to the HTTP feed will see the event at its next refresh.

So the mental model is:

secretary@domain receives invitations
ZangTumb publishes the secretary's agenda
clients subscribe to the published .ics file

Configuration

Enable WebCal with:

WEBCAL_ENABLED=true
WEBCAL_INGEST_USER=secretary
WEBCAL_LISTEN=:8088

Example:

DOMAINNAME=keinpfusch.net
MAILUSER=admin
MAILPASSWORD=a-secure-password
MAILFOLDER=/zangmail

WEBCAL_ENABLED=true
WEBCAL_INGEST_USER=secretary
WEBCAL_LISTEN=:8088

This creates the ingest address:

secretary@keinpfusch.net

and serves the generated calendar from:

http://domain/calendar.ics

The URL is protected with HTTP Basic Authentication using MAILUSER and MAILPASSWORD.

How to use it from a calendar client

In your calendar client, add a new remote calendar subscription using the HTTP URL:

http://domain/calendar.ics

Use the same credentials you use for IMAP:

username: MAILUSER
password: MAILPASSWORD

The exact wording depends on the client. It may be called:

Subscribe to calendar
Add calendar by URL
Internet calendar
Remote iCalendar
Web calendar

Do not add it as CalDAV.
This is only a read-only iCalendar feed.

Clients can read the calendar, but they cannot modify it through this URL.

How events enter the calendar

The normal workflow is to invite the WebCal ingest address to the event.

For example:

secretary@keinpfusch.net

If a calendar client creates, updates, or cancels an event and sends email notifications to the attendees, ZangTumb receives those iCalendar messages and updates the feed.

ZangTumb understands the usual iCalendar identity fields:

UID
SEQUENCE
RECURRENCE-ID
METHOD:REQUEST
METHOD:CANCEL
STATUS:CANCELLED

The UID identifies the event.

A new UID adds an event.
The same UID with a newer SEQUENCE updates the event.
METHOD:CANCEL or STATUS:CANCELLED removes the event.
RECURRENCE-ID, when present, identifies a single occurrence of a recurring event.

Events from external services

Some services, such as a barber booking website, a hotel booking system, a train company, or a medical appointment platform, may send an .ics file to the email address you provide.

In that case you may give the service the WebCal ingest address:

secretary@keinpfusch.net

If the service sends a valid .ics attachment or inline text/calendar part, ZangTumb imports it.

However, external services often create events where the original attendee is not the WebCal address. For this reason, WebCal treats the ingest mailbox as the source of truth and may normalize imported events so that the event is represented as an appointment involving:

MAILUSER@DOMAINNAME
WEBCAL_INGEST_USER@DOMAINNAME

In other words, even if the barber website does not explicitly invite the secretary in the semantic iCalendar data, the fact that the message arrived at the WebCal ingest address is enough for ZangTumb to publish it in the secretary calendar.

The practical rule is simple:

If an event reaches the WebCal ingest address,
it belongs in the WebCal feed.

Supported mail formats

ZangTumb WebCal is designed to accept the common ways calendar invitations are sent by email:

inline text/calendar
attached .ics files
Outlook / Exchange winmail.dat TNEF attachments, when TNEF support is built

This means both of these are valid:

Content-Type: text/calendar; method=REQUEST

and:

Content-Disposition: attachment; filename="invite.ics"

Outlook/Exchange may also wrap calendar data inside:

winmail.dat

When TNEF support is available, ZangTumb tries to extract calendar payloads from that file too.

Limitations

WebCal is intentionally limited.

It does not implement:

CalDAV
WebDAV
server-side calendar editing
free/busy
RSVP handling
calendar sharing permissions
push notifications

It is only a mail-driven, read-only iCalendar feed.

The client refresh interval is controlled by the client. ZangTumb does not push changes to clients.

Security notes

The calendar may contain private information.

The HTTP feed is authenticated using the same credentials as IMAP. If you expose WEBCAL_LISTEN through a reverse proxy, use HTTPS.

The ingest address should also be considered sensitive. Anyone who can send a valid calendar invitation to that address may be able to add an event to the published calendar.

For a private deployment, this is usually acceptable. For a public or hostile environment, future policy controls may be useful, such as:

accept only authenticated local senders
accept only specific sender addresses
quarantine suspicious calendar messages
limit attachment size
limit number of events per message

FAQ

In v2.0, TLS is mandatory and certificates are automatically generated if missing.

  • Why is it now a Catch-All server?

  • Because managing multiple mailboxes is for people with too much free time. You own your domain.
    One domain, one user, maximum speed.

  • Why don't you use OpenSMTPD?

  • Because making this server took less effort than containerizing OpenSMTPD properly.

  • Why don't you use Postfix/Sendmail/Qmail/Courier?

  • I host four mailboxes in total. Why should I deploy all that complexity?
    Complexity != security.

  • !ZangTumb forces StartTLS. This is against the RFC!!!!

  • Unfortunately, English cannot properly translate the correct Italian answer, which would roughly be: "Esticazzi?"

  • This Golang code is not idiomatic. And there is no graphene, no quantum computing, no UI/UX, and no horizontally scalable Internet of Things powered by Artificial Intelligence and Big Data.

  • Please, bring me a Frappuccino.

  • Why you expect the sender server has a client certificate!!! You evil!!!!

  • Is perfectly reasonable. In real life, the equivalent would be that only real couriers and national post service can put paper in your mailbox. Reasonable and legit, IMHO_