Documentation
Rail is a transactional email relay with mTLS authentication, built-in DNS, DKIM signing, and webhook-based inbound delivery.
Overview
Architecture
# Outbound (your app → recipient)
Your App → :465 (mTLS) → Rail → MX delivery + DKIM signing
# Inbound (recipient → your app)
Internet MTA → :25 (STARTTLS) → Rail → Webhook POST to your app
Ports
465 SMTP submission (mTLS, client cert required)
25 Inbound SMTP (STARTTLS, receives mail from the internet)
80 HTTP (ACME challenges, redirect to HTTPS)
443 HTTPS (management API, cert inspection, stats)
53 DNS (authoritative: MX, SPF, DKIM, DMARC)
Client Certificates
Issuance
# Issue a client cert with sender addresses and webhook URL
rail certs issue \
--config /etc/rail/rail.yaml \
--name myapp \
--email noreply@ataca.io \
--email alerts@smtp.ataca.io \
--webhook https://myapp.com/hooks/email
What's in the cert
CN Client name (e.g. "myapp") — used for audit logging
Email SANs Allowed sender addresses — MAIL FROM must match one of these
URI SANs Webhook URL (https://) — where inbound mail is delivered
Sender Authorization
# The From address must match an email SAN in the client cert.
# Local part is case-sensitive, domain is case-insensitive (RFC 5321).
# Cert has: noreply@ataca.io, alerts@smtp.ataca.io
MAIL FROM:<noreply@ataca.io> # ✓ allowed
MAIL FROM:<alerts@smtp.ataca.io> # ✓ allowed
MAIL FROM:<other@ataca.io> # ✗ 550 sender not authorized
API Reference
POST /api/v1/send — send email (mTLS required)
curl --cert client.crt --key client.key \
-X POST https://smtp.ataca.io/api/v1/send \
-H "Content-Type: application/json" \
-d '{
"request_id": "unique-id-123",
"from": "noreply@ataca.io",
"to": ["user@example.com"],
"subject": "Your order shipped",
"body_text": "Your package is on the way."
}'
# Response (202 Accepted):
{"id": "01KMT...", "status": "queued", "recipients": 1}
GET /api/who — inspect your client cert (mTLS required)
curl --cert client.crt --key client.key \
https://smtp.ataca.io/api/who
# Response:
{"client_id": "myapp", "senders": ["noreply@ataca.io"],
"valid": true, "key_type": "ed25519", ...}
POST /api/who — inspect any cert (no auth required)
curl -X POST --data-binary @client.crt \
https://smtp.ataca.io/api/who
GET /healthz, /readyz, /stats
curl https://smtp.ataca.io/healthz # 200 "ok"
curl https://smtp.ataca.io/readyz # 200 or 503 (draining)
curl https://smtp.ataca.io/stats # JSON: uptime, delivered, rate, latency
Inbound Email
How it works
# Internet MTA connects to port 25 with STARTTLS
# RCPT TO must be a local domain — non-local is rejected (no open relay)
# Message is stored and POSTed to the client's webhook URL
# Local domains are configured in rail.yaml:
local_domains: [ataca.io, smtp.ataca.io]
# Additional domains auto-discovered from client cert email SANs
# Webhook URL from client cert URI SAN (set at cert issuance)
Webhook payload
# POST to your webhook URL with JSON body:
{
"message_id": "01KMT1GZ...",
"from": "sender@gmail.com",
"to": ["noreply@ataca.io"],
"subject": "Re: Your order shipped",
"raw_message": "<base64-encoded RFC 5322 message>",
"received_at": "2026-03-28T12:00:00Z"
}
# 2xx = delivered, 4xx = permanent failure, 5xx = retry with backoff
Sending Examples
Send email via SMTP or the HTTP API. Both require a client certificate (mTLS).
Go
SMTP
import (
"crypto/tls"
"net/smtp"
)
func sendMail() error {
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
conn, _ := tls.Dial("tcp", "smtp.ataca.io:465", &tls.Config{
Certificates: []tls.Certificate{cert},
})
c, _ := smtp.NewClient(conn, "smtp.ataca.io")
c.Mail("noreply@ataca.io")
c.Rcpt("user@example.com")
w, _ := c.Data()
w.Write([]byte("Subject: Hello\r\n\r\nHi!"))
w.Close()
return c.Quit()
}
HTTP API
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
client := &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
}}
body := `{"from":"noreply@ataca.io",` +
`"to":["user@example.com"],` +
`"subject":"Hello","body_text":"Hi!"}`
resp, _ := client.Post(
"https://smtp.ataca.io/v1/send",
"application/json",
strings.NewReader(body),
)
Python
SMTP
import smtplib, ssl
ctx = ssl.create_default_context()
ctx.load_cert_chain("client.crt", "client.key")
with smtplib.SMTP_SSL("smtp.ataca.io", 465, context=ctx) as s:
s.sendmail(
"noreply@ataca.io",
["user@example.com"],
"Subject: Hello\r\n\r\nHi!",
)
HTTP API
import requests
resp = requests.post(
"https://smtp.ataca.io/v1/send",
cert=("client.crt", "client.key"),
json={
"from": "noreply@ataca.io",
"to": ["user@example.com"],
"subject": "Hello",
"body_text": "Hi!",
},
)
Certificate Inspection
mTLS GET — inspect your own client certificate
# Identify yourself: rail inspects the client cert you present
curl --cert client.crt --key client.key \
https://smtp.ataca.io/api/who
PEM POST — inspect any certificate by uploading it
# Upload a .crt or .pem file; no client cert required
curl -X POST --data-binary @client.crt \
https://smtp.ataca.io/api/who
Rust
SMTP
use lettre::{Transport, SmtpTransport, Message};
use lettre::transport::smtp::client::TlsParameters;
let email = Message::builder()
.from("noreply@ataca.io".parse()?)
.to("user@example.com".parse()?)
.subject("Hello")
.body("Hi!".to_string())?;
// Load client cert for mTLS
let tls = TlsParameters::builder("smtp.ataca.io".into())
.add_root_certificate(ca_cert)
.identity(client_identity)
.build()?;
let mailer = SmtpTransport::relay("smtp.ataca.io")?
.port(465)
.tls(lettre::transport::smtp::client::Tls::Wrapper(tls))
.build();
mailer.send(&email)?;
HTTP API
let client = reqwest::Client::builder()
.identity(identity) // mTLS client cert
.build()?;
let resp = client.post("https://smtp.ataca.io/v1/send")
.json(&serde_json::json!({
"from": "noreply@ataca.io",
"to": ["user@example.com"],
"subject": "Hello",
"body_text": "Hi!",
}))
.send().await?;