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.
- Features
- Architecture
- Quickstart
- Configuration
- Database Schema
- API Overview
- Deployment
- Domain & DNS Setup
- Sending Mail
- Testing & Demo Mode
- Development & Contributing
- License
- Cloud-native, scalable SMTP mail sending
- gRPC API for sending HTML and templated emails
- DKIM and SPF support for deliverability
- Attachments support (JSONB in DB)
- Custom fields for emails (JSONB in DB)
- Statistics and analytics (DB + gRPC API)
- Template management (CRUD via API)
- Kubernetes-ready deployment
- Postgres-backed persistence
Planned:
- Multi-node sending
- Advanced analytics dashboard
Kannon is composed of several microservices and workers:
- API: gRPC server for mail, template, and domain management
- SMTP: Handles SMTP protocol and relays mail
- Sender: Sends emails from the queue
- Dispatcher: Manages the sending pool and delivery
- Verifier: Validates emails before sending
- Bounce: Handles bounces
- Stats: Collects and stores delivery statistics
All components can be enabled/disabled via CLI flags or config.
See
ARCHITECTURE.mdfor a full breakdown of modules, NATS streams, topics, consumers, and message flows.
flowchart TD
subgraph Core
API["gRPC API"]
SMTP["SMTP Server"]
Sender["Sender"]
Dispatcher["Dispatcher"]
Verifier["Verifier"]
Bounce["Bounce"]
Stats["Stats"]
end
DB[(PostgreSQL)]
NATS[(NATS)]
API <--> DB
Dispatcher <--> DB
Sender <--> DB
Verifier <--> DB
Stats <--> DB
Bounce <--> DB
API <--> NATS
Sender <--> NATS
Dispatcher <--> NATS
SMTP <--> NATS
Stats <--> NATS
Verifier <--> NATS
Bounce <--> NATS
- Go 1.25.1+
- Docker (optional, for containerized deployment)
- PostgreSQL database
- NATS server (optional - embedded mode available in standalone command)
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.yamlThis mode:
- Runs all components (API, SMTP, Sender, Dispatcher, Verifier, Stats, Bounce)
- Embeds NATS server (no external NATS required)
- Ideal for development, testing, or single-server deployments
- Still requires a PostgreSQL database
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.yamlNote: This mode requires an external NATS server configured in your config file.
See examples/docker-compose/ for ready-to-use files.
docker-compose -f examples/docker-compose/docker-compose.yaml up- Edit
examples/docker-compose/kannon.yamlto configure your environment.
make test— Run all testsmake generate— Generate DB and proto codemake lint— Run linters
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 |
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-verifier / K_RUN_VERIFIER |
bool | false | Enable verifier worker |
run-bounce / K_RUN_BOUNCE |
bool | false | Enable bounce worker |
run-stats / K_RUN_STATS |
bool | false | Enable stats worker |
config |
string | ~/.kannon.yaml | Path to config file |
- See
examples/docker-compose/kannon.yamlfor a full example.
Kannon requires a PostgreSQL database. Main tables:
- domains: Registered sender domains, DKIM keys
- messages: Outgoing messages, subject, sender, template, attachments
- sending_pool_emails: Email queue, status, scheduling, custom fields
- templates: Email templates, type, metadata
- stats: Delivery and open/click statistics
- stats_keys: Public/private keys for stats security
See db/migrations/ for full schema and migrations.
Kannon exposes a gRPC API for sending mail, managing domains/templates, and retrieving stats.
- Mailer API (proto)
SendHTML: Send a raw HTML emailSendTemplate: Send an email using a stored template
- Admin API (proto)
GetDomains,GetDomain,CreateDomain,RegenerateDomainKeyCreateTemplate,UpdateTemplate,DeleteTemplate,GetTemplate,GetTemplates
- Stats API (proto)
GetStats,GetStatsAggregated
All gRPC APIs use Basic Auth with your domain and API key:
token = base64(<your domain>:<your domain key>)
Pass this in the Authorization metadata for gRPC calls:
{
"Authorization": "Basic <your token>"
}{
"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" }
}See the proto files for all fields and options.
- See
k8s/deployment.yamlfor a production-ready manifest. - Configure your environment via a mounted YAML file or environment variables.
- See
examples/docker-compose/for local or test deployments.
To send mail, you must register a sender domain and configure DNS:
- Register a domain via the Admin API
- 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>
- A record:
- Use the gRPC API (
SendHTMLorSendTemplate) with Basic Auth as above. - See the proto file for all fields and options.
Kannon includes a demo sender mode for testing and development without actually sending emails. This is particularly useful for:
- Development environments where you don't want to send real emails
- Testing email templates and content without affecting deliverability
- CI/CD pipelines where you need to verify email functionality
- Local development without SMTP server setup
Set sender.demo_sender: true in your configuration:
sender:
hostname: kannon.example.com
max_jobs: 10
demo_sender: true # Enable demo sender modeOr via environment variable:
export K_SENDER_DEMO_SENDER=trueWhen demo mode is enabled:
- Emails are not actually sent - they're processed through the pipeline but not delivered
- Statistics are still collected - you can track delivery attempts and errors.
- Error simulation - emails containing "error" in the recipient address will simulate delivery failures
- Full pipeline testing - all components (API, dispatcher, sender, stats) work normally
- Template processing - HTML templates and custom fields are processed correctly
This mode mocks the SMTP client and does not actually send emails.
IMPROVEMENTS:
- mock opens, clicks, etc.
- mock bounce, spam, etc.
# 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 normallyThis makes it easy to test your email integration without setting up SMTP servers or worrying about deliverability during development.
We welcome contributions! Please:
- Use feature request and bug report templates for issues
- Follow the pull request template
- See the Apache 2.0 License
- Read our CONTRIBUTING.md for guidelines, code style, and the full contribution process.
- Build:
go build -o kannon . - Test:
make testorgo test ./... - Generate code:
make generate - Lint:
make lint
- Unit and integration tests are in
internal/,pkg/, and run withgo test ./... - Some tests use Docker to spin up a test Postgres instance
- E2E tests include comprehensive email sending pipeline testing with demo sender mode
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.
The simplest way to get started is using the provided Docker Compose configuration:
cd examples/docker-compose/
docker-compose upThis will start:
- PostgreSQL database with automatic migrations
- NATS server for internal messaging
- Kannon server with all components enabled and demo sender mode activated
The API will be available at localhost:50051 (gRPC).
When using the docker-compose setup, demo sender mode is automatically enabled (demo_sender: true in kannon.yaml), which means:
- ✅ Complete pipeline testing - All API endpoints, template processing, and statistics work normally
- ✅ No real emails sent - Safe for development and testing environments
- ✅ Statistics collection - Track delivery attempts, errors, and metrics
- ✅ Error simulation - Recipients containing "error" will simulate delivery failures
- ✅ Template processing - HTML templates, custom fields, and attachments work correctly
- ✅ Authentication testing - Full gRPC API authentication flow
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-bounce: true
run-dispatcher: true
run-verifier: true
run-sender: true
run-api: true
run-stats: true-
Start the environment:
cd examples/docker-compose/ docker-compose up -d -
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
-
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
-
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
- Develop against the local API - Use
localhost:50051as your Kannon endpoint - Test email templates - Use
SendTemplateAPI with your template designs - Verify statistics - Check that your integration correctly handles delivery stats
- Test error scenarios - Send emails to recipients containing "error" to simulate failures
- Validate attachments - Test file attachments and custom fields functionality
To modify the demo environment:
- Edit
examples/docker-compose/kannon.yamlfor Kannon configuration - Edit
examples/docker-compose/docker-compose.yamlfor infrastructure changes - Restart the environment:
docker-compose down && docker-compose up
When ready for production, simply change demo_sender: false in your configuration and provide real SMTP credentials. Your integration code remains unchanged.
Kannon is licensed under the Apache 2.0 License. See LICENSE for details.