Kannon 💥

CI

Kannon Logo

A Cloud Native SMTP mail sender for Kubernetes and modern infrastructure.

[!NOTE] Due to limitations of AWS, GCP, etc. on port 25, this project will not work on cloud providers that block port 25.

Table of Contents

Features

Planned:

Architecture

A single SendHTML / SendTemplate API call creates one Batch with N Deliveries (one per Recipient). Deliveries flow through the Pool, are built into Envelopes by the Dispatcher, and transmitted by the SMTPSender. See CONTEXT.md for the full shared language (Batch, Recipient, Delivery, Envelope, Domain, Template) and the per-Delivery outcome state machine (Validated → Delivered / Bounced, plus Opened / Clicked engagement events).

Kannon is composed of several microservices and workers:

All components can be enabled/disabled via CLI flags or config.

See ARCHITECTURE.md for a full breakdown of modules, NATS streams, topics, consumers, and message flows.

flowchart TD
    subgraph Core
        API["gRPC API (Mailer / Admin / Stats)"]
        SMTPServer["SMTPServer (inbound DSN/bounce)"]
        SMTPSender["SMTPSender (outbound)"]
        Dispatcher["Dispatcher"]
        Validator["Validator"]
        Tracker["Tracker (open/click)"]
        Stats["Stats"]
    end
    DB[(PostgreSQL)]
    NATS[(NATS)]
    API <--> DB
    Dispatcher <--> DB
    SMTPSender <--> DB
    Validator <--> DB
    Stats <--> DB
    API <--> NATS
    SMTPSender <--> NATS
    Dispatcher <--> NATS
    SMTPServer <--> NATS
    Stats <--> NATS
    Validator <--> NATS
    Tracker <--> NATS

Quickstart

Prerequisites

Standalone Mode (Recommended for Development/Testing)

Run all Kannon components in a single process with embedded NATS (only PostgreSQL required):

git clone https://github.com/kannon-email/kannon.git
cd kannon
go build -o kannon .
./kannon standalone --config ./config.yaml

This mode:

Local Run (Manual Component Selection)

git clone https://github.com/kannon-email/kannon.git
cd kannon
go build -o kannon .
./kannon --run-api --run-smtp --run-sender --run-dispatcher --config ./config.yaml

Note: This mode requires an external NATS server configured in your config file.

Docker Compose

See examples/docker-compose/ for ready-to-use files.

docker-compose -f examples/docker-compose/docker-compose.yaml up

Makefile Targets

Configuration

Kannon can be configured via YAML file, environment variables, or CLI flags. Precedence: CLI > Env > YAML.

Main config options:

Key / Env Var Type Default Description
database_url / K_DATABASE_URL string (required) PostgreSQL connection string
nats_url / K_NATS_URL string (required) NATS server URL for internal messaging
debug / K_DEBUG bool false Enable debug logging
api.port / K_API_PORT int 50051 gRPC API port
sender.hostname / K_SENDER_HOSTNAME string (required) Hostname for outgoing mail
sender.max_jobs / K_SENDER_MAX_JOBS int 10 Max parallel sending jobs
sender.demo_sender / K_SENDER_DEMO_SENDER bool false Enable demo sender mode for testing
smtp.address / K_SMTP_ADDRESS string :25 SMTP server listen address
smtp.domain / K_SMTP_DOMAIN string localhost SMTP server domain
smtp.read_timeout / K_SMTP_READ_TIMEOUT duration 10s SMTP read timeout
smtp.write_timeout / K_SMTP_WRITE_TIMEOUT duration 10s SMTP write timeout
smtp.max_payload / K_SMTP_MAX_PAYLOAD size 1024kb Max SMTP message size
smtp.max_recipients / K_SMTP_MAX_RECIPIENTS int 50 Max recipients per SMTP message
tracker.port / K_TRACKER_PORT int 8080 Open/click tracking HTTP server port
run-api / K_RUN_API bool false Enable API server
run-smtp / K_RUN_SMTP bool false Enable SMTP server
run-sender / K_RUN_SENDER bool false Enable sender worker
run-dispatcher / K_RUN_DISPATCHER bool false Enable dispatcher worker
run-validator / K_RUN_VALIDATOR bool false Enable validator worker
run-tracker / K_RUN_TRACKER bool false Enable tracker worker
run-stats / K_RUN_STATS bool false Enable stats worker
config string ~/.kannon.yaml Path to config file

Deprecated aliases: run-verifier / K_RUN_VERIFIER continue to work as aliases for run-validator / K_RUN_VALIDATOR; run-bounce / K_RUN_BOUNCE continue to work as aliases for run-tracker / K_RUN_TRACKER; and the bump: YAML section / K_BUMP_PORT env var continue to work as aliases for tracker: / K_TRACKER_PORT. They will be removed in a future major version.

Database Schema

Kannon requires a PostgreSQL database. Main tables (physical names retained for backward compatibility; see CONTEXT.md for the corresponding domain entities):

See db/migrations/ for full schema and migrations.

API Overview

Kannon exposes a gRPC API for sending mail, managing domains/templates, and retrieving stats.

Services & Methods

Authentication

All gRPC APIs use Basic Auth with your domain and API key:

token = base64(<your domain>:<your api key>)

Pass this in the Authorization metadata for gRPC calls:

{
  "Authorization": "Basic <your token>"
}

Example: SendHTML Request

{
  "sender": {
    "email": "no-reply@yourdomain.com",
    "alias": "Your Name"
  },
  "recipients": ["user@example.com"],
  "subject": "Test",
  "html": "<h1>Hello</h1><p>This is a test.</p>",
  "attachments": [
    { "filename": "file.txt", "content": "base64-encoded-content" }
  ],
  "fields": { "custom": "value" },
  "headers": {
    "to": ["visible-recipient@example.com"],
    "cc": ["cc@example.com"]
  }
}

Headers

The optional headers field allows overriding the To and adding a Cc header on sent emails. The SMTP envelope recipient (actual delivery target) remains the pool recipient, but the visible mail headers will use the values from headers:

This is useful for scenarios where you want the email to appear addressed to a group or alias while delivering to individual recipients.

See the proto files for all fields and options.

Deployment

Kubernetes

Docker Compose

Domain & DNS Setup

To send mail, you must register a sender domain and configure DNS:

  1. Register a domain via the Admin API
  2. Set up DNS records:
    • A record: <SENDER_NAME> → your server IP
    • Reverse DNS: your server IP → <SENDER_NAME>
    • SPF TXT: <SENDER_NAME> → v=spf1 ip4:<YOUR SENDER IP> -all
    • DKIM TXT: smtp._domainkey.<YOUR_DOMAIN> → k=rsa; p=<YOUR DKIM KEY HERE>

Sending Mail

Testing & Demo Mode

Kannon includes a demo sender mode for testing and development without actually sending emails. This is particularly useful for:

Enabling Demo Mode

Set sender.demo_sender: true in your configuration:

sender:
  hostname: kannon.example.com
  max_jobs: 10
  demo_sender: true # Enable demo sender mode

Or via environment variable:

export K_SENDER_DEMO_SENDER=true

Demo Sender Behavior

When demo mode is enabled:

This mode mocks the SMTP client and does not actually send emails.

IMPROVEMENTS:

Example: Testing with Demo Mode

# Start Kannon with demo sender enabled
./kannon --run-api --run-sender --run-dispatcher \
  --sender-demo-sender \
  --sender-hostname test.example.com \
  --config ./config.yaml

# Send test emails via API
# Emails will be processed but not actually sent
# Statistics will be collected normally

This makes it easy to test your email integration without setting up SMTP servers or worrying about deliverability during development.

Development & Contributing

We welcome contributions! Please:

Developer Documentation

Local Development

Testing

Local Testing for Integrations Development

For developers building integrations with Kannon, we provide a complete local testing environment using Docker Compose with demo sender mode. This allows you to test the entire email pipeline without actually sending emails or requiring SMTP server setup.

Quick Start with Docker Compose

The simplest way to get started is using the provided Docker Compose configuration:

cd examples/docker-compose/
docker-compose up

This will start:

The API will be available at localhost:50051 (gRPC).

Demo Sender Mode Benefits

When using the docker-compose setup, demo sender mode is automatically enabled (demo_sender: true in kannon.yaml), which means:

Configuration

The demo environment uses the configuration in examples/docker-compose/kannon.yaml:

sender:
  hostname: kannon.ludusrusso.dev
  max_jobs: 100
  demo_sender: true # Mock SMTP sending

# All components enabled for full testing
run-smtp: true
run-tracker: true
run-dispatcher: true
run-validator: true
run-sender: true
run-api: true
run-stats: true

Testing Your Integration

  1. Start the environment:

    cd examples/docker-compose/
    docker-compose up -d
    
  2. Create a test domain (using grpcurl or your gRPC client):

    # Register a domain for testing
    grpcurl -plaintext -d '{"domain":"test.example.com"}' \
      localhost:50051 kannon.admin.apiv1.AdminApiV1Service/CreateDomain
    
  3. Send test emails via the API:

    # Send HTML email (will be processed but not actually sent)
    grpcurl -plaintext \
      -H "Authorization: Basic $(echo -n 'test.example.com:your-domain-key' | base64)" \
      -d '{"sender":{"email":"test@test.example.com","alias":"Test"},"recipients":["user@example.com"],"subject":"Test Email","html":"<h1>Hello World</h1>"}' \
      localhost:50051 kannon.mailer.apiv1.MailerApiV1Service/SendHTML
    
  4. Check statistics:

    # View delivery stats (will show processed emails)
    grpcurl -plaintext \
      -H "Authorization: Basic $(echo -n 'test.example.com:your-domain-key' | base64)" \
      localhost:50051 kannon.stats.apiv1.StatsApiV1Service/GetStats
    

Integration Development Workflow

  1. Develop against the local API - Use localhost:50051 as your Kannon endpoint
  2. Test email templates - Use SendTemplate API with your template designs
  3. Verify statistics - Check that your integration correctly handles delivery stats
  4. Test error scenarios - Send emails to recipients containing "error" to simulate failures
  5. Validate attachments - Test file attachments and custom fields functionality

Customizing the Environment

To modify the demo environment:

  1. Edit examples/docker-compose/kannon.yaml for Kannon configuration
  2. Edit examples/docker-compose/docker-compose.yaml for infrastructure changes
  3. Restart the environment: docker-compose down && docker-compose up

Production Transition

When ready for production, simply change demo_sender: false in your configuration and provide real SMTP credentials. Your integration code remains unchanged.

License

Kannon is licensed under the Apache 2.0 License. See LICENSE for details.