docs
Operational
Documentation
Rail is a transactional email relay with mTLS authentication, built-in DNS, DKIM signing, and webhook-based inbound delivery.
Overview
# 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
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
# 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
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
# 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
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}
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", ...}
curl -X POST --data-binary @client.crt \
  https://smtp.ataca.io/api/who
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
# 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)
# 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
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()
}
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
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!",
  )
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
# Identify yourself: rail inspects the client cert you present
curl --cert client.crt --key client.key \
  https://smtp.ataca.io/api/who
# Upload a .crt or .pem file; no client cert required
curl -X POST --data-binary @client.crt \
  https://smtp.ataca.io/api/who
Rust
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)?;
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?;