Skip to main content

README.md

<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>