Skip to content

Email / SMTP (Meeting Invites)

Status: Complete

Wire Chatalot to an outbound mail relay so it can send calendar invites when meetings are scheduled, updated, or cancelled.

What It Does

When a meeting is created, updated, or cancelled in Chatalot, the server sends each human channel member a calendar invite as an .ics attachment -- the same format used by Google Calendar, Outlook, and Apple Calendar. Bot accounts are skipped. Sends are fire-and-forget: a delivery failure for one recipient is logged and skipped; it does not fail the scheduling operation or block other recipients.

Email Is Opt-In

If SMTP_HOST or EMAIL_FROM is not set, outbound email is silently disabled. Every other feature -- messaging, voice, file uploads, communities -- continues to work normally. There is no degraded mode, no error on startup, and no user-visible indication that email is unconfigured. Configure it when you want invite emails; leave it out when you do not.

Email is enabled only when both SMTP_HOST and EMAIL_FROM are set.

Security & Privacy

Chatalot is privacy-first, and a calendar invite necessarily leaves your server's boundary for attendees' mail providers. The feature is built to send the minimum required and to protect the transport:

  • Transport encryption. With SMTP_TLS=true (the default) Chatalot uses STARTTLS and validates the relay's TLS certificate — a relay with an invalid/expired/self-signed certificate is rejected, not silently accepted. There is no "accept invalid certificate" option. Use SMTP_TLS=false (plaintext) only for a trusted local relay or test catcher on the same host/private network; it logs a startup warning. Never point a plaintext connection at a remote relay — credentials and mail would travel unencrypted.
  • What data is sent. Each attendee receives an invite carrying only: the meeting title, start/end time + timezone, the organizer (name + address), themselves as the sole attendee, and the in-app join link. It deliberately does not include the meeting's free-text description (that stays in-app, behind the join link) and does not include the other attendees' addresses — each recipient's .ics lists only the organizer and themselves, so the attendee roster is never disclosed across recipients. No message history or end-to-end-encrypted content is ever emailed.
  • Bots excluded. Bot/automation accounts are never emailed.
  • Rate-limited. Rapid edits to a meeting are throttled (one invite email per meeting per minute) so a burst of changes can't spam attendees; cancellations always send.
  • Deliverability is your responsibility. SPF, DKIM, and DMARC for your sending domain are operator-configured (see Deliverability below). Without them, invites will be spam-foldered or rejected.
  • Credentials. Provide SMTP_PASSWORD via an env var or a Docker secret; Chatalot never logs it. Use a least-privilege, app-scoped credential where your provider supports one.
  • Failure is silent to users. A mail outage never blocks scheduling and is never surfaced to the person scheduling — failures are logged server-side only.

Environment Variables

Variable Description Default
SMTP_HOST Hostname of your SMTP relay (e.g. smtp.mailgun.org) (none -- email disabled)
SMTP_PORT SMTP port 587
SMTP_USERNAME SMTP authentication username (none)
SMTP_PASSWORD SMTP authentication password (see "Secrets" below) (none)
SMTP_TLS true = STARTTLS + auth (real relays, default); false = plaintext only (local catchers) true
EMAIL_FROM Envelope and From: address (e.g. noreply@example.com) (none -- email disabled)
EMAIL_FROM_NAME Display name in the From: header Chatalot

SMTP_PASSWORD -- env var or Docker secret

The password is resolved in this order:

  1. SMTP_PASSWORD environment variable (direct value in .env).
  2. /run/secrets/smtp_password -- a Docker secret file. Use this to keep the password out of your compose environment entirely.

If neither is present, authentication is attempted without a password (appropriate for local catchers that require no auth).

STARTTLS vs Plaintext

SMTP_TLS=true (the default) connects with STARTTLS and authenticates. Use this for every real mail relay -- Gmail, SES, Mailgun, SendGrid, Postfix, Proton Bridge, etc.

SMTP_TLS=false uses plaintext SMTP with no TLS. This exists exclusively for local development catchers (e.g. Mailpit) that accept unauthenticated plaintext on a loopback port. Never point SMTP_TLS=false at an internet-facing relay -- credentials will be transmitted in the clear.

From Address and Display Name

EMAIL_FROM must be a valid RFC 5321 address your relay is authorised to send from. If EMAIL_FROM_NAME is omitted, recipients will see Chatalot <noreply@example.com> in their mail client.

Choose an address on a domain you control and for which you have set up SPF, DKIM, and DMARC (see "Deliverability" below). A mismatch between EMAIL_FROM and the domain your relay authenticates will cause rejections at destinations that enforce DMARC.

Deliverability

Chatalot sends the email; getting it accepted and placed in the inbox is the responsibility of the operator. Three DNS records protect your sending reputation and are required by most modern mail servers:

SPF -- A TXT record on your sending domain that lists which mail servers are authorised to send on its behalf. Example for a single relay at mail.example.com:

example.com. TXT "v=spf1 mx include:mail.example.com ~all"

DKIM -- Your relay signs outgoing messages with a private key; a TXT record publishes the corresponding public key. The exact setup depends on your relay software or provider -- consult their documentation. Without a valid DKIM signature, many providers will reject your mail or route it to spam.

DMARC -- A TXT record that tells receiving servers what to do when SPF or DKIM fails. Start with p=none (report only) while you verify your configuration, then tighten to p=quarantine or p=reject:

_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"

A domain without these records, or with a misconfigured p=reject policy and no working DKIM, will have most invites delivered directly to spam or rejected outright. If you are sending from a shared relay domain (e.g. a transactional service's subdomain), the relay provider handles SPF/DKIM on their end -- confirm this in their documentation.

Container-to-Host SMTP Relay

If your SMTP relay runs on the same machine as Chatalot (for example, a Postfix instance or a local Mailpit catcher), the chatalot container cannot reach it via localhost or 127.0.0.1 -- those addresses resolve to the container itself, not the host.

Use Docker's host-gateway feature instead. In docker-compose.yml, add an extra_hosts entry:

services:
  chatalot:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Then set:

SMTP_HOST=host.docker.internal

The chatalot container will route SMTP traffic to the Docker host's network stack, where your relay is listening.

Worked Examples

Generic authenticated relay (STARTTLS, port 587)

SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=noreply@example.com
SMTP_PASSWORD=your-smtp-password
SMTP_TLS=true
EMAIL_FROM=noreply@example.com
EMAIL_FROM_NAME=Chatalot

Amazon SES (SMTP interface)

Create SMTP credentials from the SES console (IAM user with ses:SendRawEmail). SES requires STARTTLS on port 587 or 465.

SMTP_HOST=email-smtp.us-east-1.amazonaws.com   # adjust region
SMTP_PORT=587
SMTP_USERNAME=AKIA...                           # SMTP access key ID from SES
SMTP_PASSWORD=your-ses-smtp-secret-key
SMTP_TLS=true
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=Your Instance Name

Verify your sending domain in the SES console and ensure the account is out of the sandbox before sending to non-verified recipients.

Mailgun / SendGrid

Both services provide SMTP credentials in their dashboard. The pattern is the same as the generic relay example; substitute the hostname and credentials from your provider.

Mailgun:

SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USERNAME=postmaster@mg.yourdomain.com
SMTP_PASSWORD=your-mailgun-smtp-password
SMTP_TLS=true
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=Chatalot

SendGrid:

SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=SG.your-api-key
SMTP_TLS=true
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=Chatalot

Self-hosted Postfix (relay on Docker host)

# docker-compose.yml (add to the chatalot service)
services:
  chatalot:
    extra_hosts:
      - "host.docker.internal:host-gateway"
# .env
SMTP_HOST=host.docker.internal
SMTP_PORT=587
SMTP_USERNAME=chatalot@yourdomain.com
SMTP_PASSWORD=your-postfix-password
SMTP_TLS=true
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME=Chatalot

Ensure Postfix is configured to accept authenticated submissions on port 587 (submission service in master.cf) and that your Chatalot user is present in the credential store (e.g. /etc/sasldb2).

Proton Mail Bridge (Seglamater's own setup -- an example, not required)

Note: This is how Seglamater runs its own Chatalot instance. It is included as a concrete reference only. There is no requirement to use Proton Mail Bridge; it is one of many valid choices.

Proton Mail Bridge runs as a local SMTP daemon that bridges Proton Mail accounts to standard SMTP clients. It does not use STARTTLS on the local port -- it expects a plaintext connection from the container and handles encryption itself on the outbound side.

# docker-compose.yml
services:
  chatalot:
    extra_hosts:
      - "host.docker.internal:host-gateway"
# .env
SMTP_HOST=host.docker.internal
SMTP_PORT=1025             # Bridge's local SMTP port (check Bridge settings)
SMTP_USERNAME=your@proton.me
SMTP_PASSWORD=your-bridge-smtp-password
SMTP_TLS=false             # Bridge handles TLS on the outbound side; local connection is plaintext
EMAIL_FROM=your@proton.me
EMAIL_FROM_NAME=Chatalot

Verifying Your Configuration

Before pointing at a real relay, test locally with Mailpit -- a zero-config SMTP catcher with a web UI that shows every email the server sends without delivering anything.

# Run Mailpit
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
# .env -- point chatalot at the local catcher
SMTP_HOST=host.docker.internal
SMTP_PORT=1025
SMTP_TLS=false         # required; Mailpit does not use STARTTLS
EMAIL_FROM=test@example.com
EMAIL_FROM_NAME=Chatalot Test

Open http://localhost:8025 to see captured emails. Once invites appear there, switch SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, and SMTP_TLS to your real relay values.

Next Step

For general environment variable reference, see Configuration. For hardening advice (file permissions, secrets management), see Security Hardening.