Features
SDKs
Official clients for the languages you already write in, plus framework integrations, each covering the same send surface and verifying your webhooks.
One client per language, one surface
Every client covers the same ground: sending, attachments, tags, templates, scheduled sends, idempotency keys, typed errors and webhook signature verification. What changes is the idiom: each client takes the shape its language already uses.
| Language | Package | Floor |
|---|---|---|
| Node | @mailkube/mailkube-node | TypeScript, ESM and CommonJS |
| Python | mailkube | 3.12+ |
| Go | github.com/mailkube/mailkube-go | 1.23+ |
| Java | com.mailkube:mailkube-java | JVM 25+ |
| PHP | mailkube/mailkube-php | 8.3+ |
| Ruby | mailkube-ruby | 3.4+ |
What they cost your dependency tree
Node, Go, Java and Ruby ship with zero runtime dependencies. The Java POM has no <dependencies> block at all, JSON included. The PHP client talks PSR-18 and uses whatever HTTP client your application already has. Python is the exception, building on httpx and pydantic.
Each language’s own conventions
The Python client ships Mailkube and AsyncMailkube with an identical method surface. Moving a service to async is a different constructor and an await. Java takes the opposite route: one synchronous client, with concurrency left to virtual threads and JVM 25 as the floor. JEP 491 is what made that safe. Go gives you one client that every goroutine can share. PHP uses named arguments and readonly models throughout, and Ruby uses keyword arguments and typed response models. Node ships one build that runs on servers, workers and edge runtimes.
Errors follow the same rule. Each client raises its language’s idea of an error hierarchy. The error carries the API’s error_name, the status code, the request id worth quoting to support, and a retry delay on the rate-limit case.
Receiving events as well as sending them
Each client handles the inbound half too. Hand it the raw request body and the request headers. It compares the signature in constant time and applies the 300-second freshness window that rejects a replayed delivery. What you get back is a typed event your switch or case can branch on. Java goes as far as a sealed interface. A switch with no default arm stops compiling when a new event type appears.
Go ships a WebhookHandler that is an http.Handler and mounts anywhere. Mount it on GET as well as POST: the URL is probed with a hub.challenge before the endpoint is saved at all.
Every client also carries the signing half. A test can build a delivery your handler accepts without waiting for a real one. X-Webhook-Id is stable across retries, so deduplicate on it before you act.
Framework integrations
Four frameworks get a package of their own, each one meeting the framework where it already sends mail.
| Framework | Package | Plugs into | Requires |
|---|---|---|---|
| Django | mailkube-django | EMAIL_BACKEND | Django 5.2+ |
| Laravel | mailkube/mailkube-laravel | a MAIL_MAILER transport | Laravel 12 or 13 |
| Rails | mailkube-rails | an Action Mailer delivery method | Rails 7.2.3+ |
| Spring Boot | com.mailkube:mailkube-spring-boot | the MailSender bean | Boot 3.5.0+ |
All four are thin adapters over the language client. The API calls, the retries, the errors and the signature check stay in one place. Your mailers, your Mail facade calls and your send_mail stay as they are. What changes is one configuration line. Inbound events arrive as whatever that framework already uses for events. That is Laravel events, ActiveSupport::Notifications in Rails, Spring application events, and a WebhookView you mount in urls.py for Django. Django ships two backends in one distribution, one standalone and one built on django-anymail for applications already using it.
Elsewhere the language client and a little wiring are all it takes. The docs carry that wiring worked out: Flask(opens in a new tab) and FastAPI(opens in a new tab) for Python, Gin(opens in a new tab) , Echo(opens in a new tab) and Fiber(opens in a new tab) for Go. Each one shows where the client belongs in that framework’s lifecycle and how to mount the webhook receiver.
For a language with no client of its own, the relay sidecar(opens in a new tab) runs the mailkube SMTP relay container next to your application. That gives it a local durable send queue and keeps the credential in one place.
Per-language documentation
The SDK documentation(opens in a new tab) is the place to start. Pick your language and its page opens with an install line and a send. From there it covers attachments, reply threading, idempotency, scheduled sends, webhook handling and the error reference for that client.