<p align="center">
<a href="https://mailkite.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://mailkite.dev/brand/logo-email-dark.png">
<img src="https://mailkite.dev/brand/logo-email.png" alt="MailKite" height="56">
</picture>
</a>
</p>
<h1 align="center">MailKite for Elixir</h1>
<p align="center">
<b>Email for every product you ship</b> — receive email as a webhook, send over a verified domain, give an AI agent its own inbox.
<br>The official <a href="https://mailkite.dev">MailKite</a> library for Elixir.
</p>
<p align="center">
<a href="https://mailkite.dev/docs">Docs</a> ·
<a href="https://mailkite.dev/docs/libraries">Library guide</a> ·
<a href="https://mailkite.dev">mailkite.dev</a> ·
<a href="https://mailkite.dev/docs/ai-agents">AI agents</a>
</p>
<p align="center"><a href="https://hex.pm/packages/mailkite"><img src="https://img.shields.io/hexpm/v/mailkite?color=2563eb&label=Hex" alt="Hex"></a></p>
> **Read-only mirror.** This repo is a generated, release-time mirror of the MailKite monorepo (the private source of truth) — development doesn't happen here. Install from Hex and open issues against the [MailKite docs](https://mailkite.dev/docs).
## Install
Add `:mailkite` to `deps` in `mix.exs`:
```elixir
def deps do
[{:mailkite, "~> 0.13"}]
end
```
Then `mix deps.get`. The library has **zero third-party runtime dependencies** — HTTP is stdlib `:httpc`/`:inets`, crypto is stdlib `:crypto`/`:public_key`, and JSON uses the built-in `JSON` module on Elixir 1.18+ (falling back to `Jason` on older Elixir).
## Quickstart
```elixir
mk = MailKite.new(System.get_env("MAILKITE_API_KEY"))
{:ok, res} =
MailKite.Methods.send(mk, %{
"from" => "hello@myapp.ai",
"to" => "ada@example.com",
"subject" => "Your invoice #1042",
"html" => "<p>Thanks! Receipt attached.</p>"
})
```
Every method returns `{:ok, value}` on success (the parsed JSON response) or `{:error, %MailKite.Error{}}` on any non-2xx response — pattern-match to handle both:
```elixir
case MailKite.Methods.send(mk, message) do
{:ok, res} -> IO.puts("queued #{res["id"]}")
{:error, %MailKite.Error{status: status, message: msg}} -> IO.puts("failed #{status}: #{msg}")
end
```
## Authentication — API key or OAuth
The credential is always a Bearer token, so an **OAuth access token** works anywhere an API key does. **Server-to-server code → API key; anything that renders on a public URL → OAuth** (each user acts as themselves, not through a shared key).
```elixir
# Server-to-server: a static API key (mk_live_…).
mk = MailKite.new(System.get_env("MAILKITE_API_KEY"))
# OAuth: a static access token…
mk = MailKite.Client.new(access_token: my_oauth_token)
# …or a get_token callback called before each request, so short-lived
# OAuth access tokens stay fresh:
mk = MailKite.Client.new(get_token: fn -> current_session_access_token() end)
```
Point at a custom base URL with `MailKite.new(api_key, "https://api.mailkite.dev")` or `MailKite.Client.new(access_token: token, base_url: url)`.
Get an OAuth token from MailKite's authorization server (`mcp.mailkite.dev`, OAuth 2.1 + PKCE). For browser/native apps use the client-side libraries, which run the whole flow.
## Verify inbound webhooks
`MailKite.Webhook` is entirely local — no network call. Verify the `x-mailkite-signature` header over the **raw** request body before trusting an inbound event, then return one of the reply bodies:
```elixir
if MailKite.Webhook.verify_webhook(signature, raw_body, webhook_secret) do
# process the event…
MailKite.Webhook.reply_ok() # {"status":"ok"}
else
# reject
end
# Control-mode replies a handler can return:
MailKite.Webhook.reply_spam() # {"status":"spam"}
MailKite.Webhook.reply_drop() # {"status":"drop"}
MailKite.Webhook.reply_block_sender() # {"status":"ok","actions":[{"type":"block-sender"}]}
```
`verify_webhook/4` rejects events older than a 5-minute replay window by default; pass a fourth argument (ms, or `0` to disable) to change it.
## At-rest encryption
`MailKite.Crypto` encrypts to an RSA public key and decrypts with the private key. The envelope is byte-compatible with every other MailKite SDK and MailKite's own WebCrypto, so a value encrypted in one language decrypts in another.
```elixir
envelope = MailKite.Crypto.encrypt("secret note", public_key_pem)
{:ok, plaintext} = MailKite.Crypto.decrypt(envelope, private_key_pem)
```
## Attachments
Upload a file once and reference the returned URL on sends, instead of base64-inlining it every time. Provide the file as a `url` (re-hosted by MailKite), raw `bytes`, a local `path`, or base64 `content`:
```elixir
{:ok, up} =
MailKite.Client.upload_attachment(mk, %{
"path" => "/tmp/receipt.pdf",
"filename" => "receipt.pdf"
})
MailKite.Methods.send(mk, %{
"from" => "billing@myapp.ai",
"to" => "ada@example.com",
"subject" => "Your receipt",
"text" => "Attached.",
"attachments" => [%{"filename" => up["filename"], "url" => up["url"]}]
})
```
## API methods
Every endpoint is a function in `MailKite.Methods`, taking the client first. Names are snake_case (e.g. `list_domains/1`, `get_template/2`, `send_broadcast/3`). The full surface: `send`, `upload_attachment`, `list_templates`, `list_base_templates`, `get_template`, `create_template`, `list_domains`, `create_domain`, `get_domain`, `delete_domain`, `verify_domain`, `set_webhook`, `delete_webhook`, `test_webhook`, `check_domain_availability`, `register_domain`, `list_routes`, `create_route`, `delete_route`, `agent`, `route`, `list_messages`, `get_message`, `retry_delivery`, `list_lists`, `create_list`, `get_list`, `update_list`, `delete_list`, `list_list_contacts`, `add_list_contacts`, `remove_list_contact`, `list_broadcasts`, `create_broadcast`, `get_broadcast`, `update_broadcast`, `delete_broadcast`, `send_broadcast`, `semantic_search`, plus the local `MailKite.Webhook` and `MailKite.Crypto` helpers.
List endpoints take a keyword of pagination options:
```elixir
{:ok, msgs} = MailKite.Methods.list_messages(mk, before: 1_750_000_000_000, limit: 50, search: "invoice")
```
## Use it from an AI agent — MCP + Agent connectors
MailKite speaks the [Model Context Protocol](https://modelcontextprotocol.io): every API method is a tool your AI assistant (Claude, Cursor, …) can call. Full guide: **[https://mailkite.dev/docs/ai-agents](https://mailkite.dev/docs/ai-agents)**.
**Hosted (recommended) — one-click OAuth, no key to copy:**
```bash
claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp
```
**Local (static key, offline / CI):**
```json
{ "mcpServers": { "mailkite": { "command": "npx", "args": ["-y", "@mailkite/mcp"], "env": { "MAILKITE_API_KEY": "mk_live_…" } } } }
```
## All MailKite libraries
Same contract, every language — pick the one for your stack (full list: [https://mailkite.dev/docs/libraries](https://mailkite.dev/docs/libraries)):
| Library | Repo | Distribution |
| --- | --- | --- |
| MailKite for Node.js | [`mailkite-node`](https://github.com/mailkite/mailkite-node) | npm |
| MailKite for Python | [`mailkite-python`](https://github.com/mailkite/mailkite-python) | PyPI |
| MailKite for Ruby | [`mailkite-ruby`](https://github.com/mailkite/mailkite-ruby) | RubyGems |
| MailKite for Elixir **(this repo)** | [`mailkite-elixir`](https://github.com/mailkite/mailkite-elixir) | Hex |
| MailKite for Java | [`mailkite-java`](https://github.com/mailkite/mailkite-java) | Maven Central |
| MailKite for PHP | [`mailkite-php`](https://github.com/mailkite/mailkite-php) | Packagist |
| MailKite for Go | [`mailkite-go`](https://github.com/mailkite/mailkite-go) | Go modules |
| @mailkite/cli | [`mailkite-cli`](https://github.com/mailkite/mailkite-cli) | npm |
| @mailkite/mcp | [`mailkite-mcp`](https://github.com/mailkite/mailkite-mcp) | npm |
## Docs & links
- 📚 **Documentation:** https://mailkite.dev/docs
- 📦 **This library's guide:** https://mailkite.dev/docs/libraries
- 🤖 **AI agents (MCP + inbox agents):** https://mailkite.dev/docs/ai-agents
- 🌐 **Website:** https://mailkite.dev
<sub>Generated from the shared MailKite API contract. © MailKite. MIT licensed.</sub>