Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

📧 Libver SMTP Service

Node.js Version Service Status License Security

A simple, self-hosted Mail Transfer Agent (MTA) for libver.gr that handles the full email delivery pipeline, from message submission to direct delivery to recipient mail servers (Gmail, Outlook, etc.) using SMTP.

Running your own MTA requires proper DNS, reverse DNS, and IP reputation; misconfiguration can lead to emails being rejected or marked as spam.


✨ Why this exists

This project was made as part of an internship abroad at the Central Public Library of Veria and is a part of https://events.libver.gr (open-source github repo) that was made for the library.

Transactional emails like password resets, confirmation receipts, and notifications are necessary for the event registration system, this standalone service ensures:

  • Privacy: No third-party provider has access to your email data or logs.
  • Reliability: A Redis-backed queue ensures that if a destination server is temporarily down, the email isn't lost; it just waits and retries.
  • Authority: Every email is signed with DKIM (RSA-2048).

🧩 Stack

  • Node.js (>=20)
  • Fastify - API server
  • BullMQ - Queue management
  • Redis - Durable message queue
  • smtp-server - SMTP listener
  • Nodemailer - Outbound SMTP and DKIM signing
  • mailparser - Email parsing
  • Pino - Logging
  • Docker & docker-compose - Containerization

🏗️ How it works

This service acts as a lightweight MTA:

  1. Inbound (SMTP) Listens on port 2525 for the Laravel application to submit emails.
  2. Queueing Messages are pushed into Redis using BullMQ for durability and retry handling.
  3. MX Resolution The worker performs real-time DNS MX lookups to determine the recipient’s mail server.
  4. DKIM Signing Each message is signed using a private RSA-2048 key.
  5. Outbound Delivery A secure SMTP connection (STARTTLS) is established and the message is delivered directly to the destination server.

Security-wise, this service includes:

  • Stream parsing to prevent "buffer bomb" attacks.
  • Brute-force protection for SMTP and API credentials.
  • Network isolation as Redis is not exposed publicly; internal services are bound to 127.0.0.1.
  • Runs as a non-root user (appuser) inside the container.

🛠️ Setup DNS

To land in the Inbox, you must configure SPF, DKIM, and DMARC in your DNS (e.g., Cloudflare).

1. The SPF Record (Sender Policy Framework)

Type: TXT  |  Name: @  |  Value: v=spf1 ip4:YOUR_SERVER_IP -all

If you already have an SPF record (for example from Google Workspace, Outlook, etc.), you need to merge them into a single record, having multiple SPF records will cause SPF validation to fail.

Incorrect (multiple SPF records):

v=spf1 include:_spf.google.com -all
v=spf1 ip4:YOUR_SERVER_IP include:_spf.mx.cloudflare.net -all

Correct (merged into one):

v=spf1 ip4:YOUR_SERVER_IP include:_spf.mx.cloudflare.net include:_spf.google.com -all

2. The DKIM Record (mail._domainkey)

Type: TXT  |  Name: mail._domainkey  |  Value: v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMI... (See dkim-public.pem)

You can have multiple DKIM records (unlike SPF), each one is identified by a selector (in this case: mail in mail._domainkey). This is good as it allows key rotation, you can add a new selector (e.g. mail2._domainkey) without breaking existing emails.

⚠️ The selector used by your application must match the DNS record. For example, if your service signs emails with the selector mail, the DNS record must be mail._domainkey.

Long keys are often split into multiple quoted strings. DNS providers usually handle this automatically, but formatting errors will break verification.

3. The DMARC Record

Type: TXT  |  Name: _dmarc  |  Value: v=DMARC1; p=reject; rua=mailto:admin@libver.gr;

⚠️ DMARC requires alignment, the domain used in DKIM/SPF must align with the From: domain, otherwise DMARC will fail.

p=reject is strict which is great for security, but misconfiguration can cause legitimate emails to be rejected so it's safer to start with p=none; then monitor reports, and move to quarantine -> reject

You will receive DMARC reports in XML files sent by providers (Google, Microsoft, etc.) showing who is sending emails as your domain and the pass/fail rates

Make sure the rua email exists and can receive mail, otherwise you lose visibility into issues.


🚀 Deployment

Getting the engine running is straightforward.

  1. Configure Environment: Copy .env.example to .env and set your internal passwords.
  2. Spin up the Stack:
    docker-compose up -d --build
  3. Verify Health: Visit http://localhost:3000/health to see the heart beating.

Make sure your environment is properly configured, as without them, emails will likely be rejected or flagged as spam:

  • Static IP address
  • Reverse DNS (PTR) → resolves to mail.libver.gr
  • Port 25 outbound enabled

🧪 Testing the Pipeline

Once deployed, you can verify everything is working by checking the logs:

docker-compose logs -f app

Note: During local or CI testing, emails are not sent to real recipients. Instead, the service uses Ethereal Email via Nodemailer, allowing you to preview sent emails in a web UI

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages