# Elixir

Use the official [Lettermint Elixir SDK](https://github.com/lettermint/lettermint-elixir) to send email from an Elixir application. The SDK also supports the Team API and webhook signature verification. See [SDKs and integrations](/sdks) for other packages.

## Requirements

Before you start, you need:

- Elixir 1.15 or newer with a compatible Erlang/OTP release
- A Mix project
- A [Lettermint account](https://app.lettermint.co) with a [verified sending domain](/platform/domains/introduction)
- A Project API token from your [project settings](https://app.lettermint.co/projects)

## 1. Installation

Add the [Lettermint Hex package](https://hex.pm/packages/lettermint) to the existing dependency list in `mix.exs`:

```elixir title="mix.exs"
defp deps do
  [
    {:lettermint, "~> 1.0"}
  ]
end
```

Install the dependency:

```bash
mix deps.get
```

The SDK uses Req for HTTP requests and Jason for JSON. Mix installs these dependencies. No application configuration is required.

## 2. Send your first email

Set `LETTERMINT_PROJECT_TOKEN` in your application's environment. Use a Project API token for email sends.

Create a script in your project directory:

```elixir title="send_email.exs"
client = Lettermint.email(System.fetch_env!("LETTERMINT_PROJECT_TOKEN"))

{:ok, message} =
  Lettermint.Email.send(client, %{
    from: "Example <hello@yourdomain.com>",
    to: ["recipient@example.com"],
    subject: "Hello from Lettermint",
    html: "<h1>Hello!</h1><p>This is a test email.</p>",
    text: "Hello! This is a test email."
  })

IO.puts("Email ID: #{message.message_id}")
IO.puts("Status: #{message.status}")
```

Replace the sender with an address on your verified domain. Replace the recipient with your test address, then run the script:

```bash
mix run send_email.exs
```

API calls return `{:ok, result}` or `{:error, %Lettermint.Error{}}`. The first example matches a successful response. Use a `case` expression to handle failures, as shown in the error section below.

## 3. Email options

The examples below use the `client` from the previous section. Request maps accept atom or string keys.

### Recipients, reply-to, and headers

```elixir
Lettermint.Email.send(client, %{
  from: "Support <support@yourdomain.com>",
  to: ["customer@example.com", "colleague@example.com"],
  cc: ["manager@yourdomain.com"],
  bcc: ["archive@yourdomain.com"],
  reply_to: ["help@yourdomain.com"],
  subject: "Support ticket 12345",
  text: "Your support ticket has an update.",
  headers: %{"X-Ticket-ID" => "12345"}
})
```

The payload's `headers` field sets email headers. Use the `idempotency_key` request option for the HTTP idempotency header.

### Tags and metadata

```elixir
Lettermint.Email.send(client, %{
  from: "orders@yourdomain.com",
  to: ["customer@example.com"],
  subject: "Order 12345 confirmed",
  text: "Your order is confirmed.",
  tags: [%{name: "category", value: "order-confirmation"}],
  metadata: %{order_id: "12345", customer_id: "cust_789"}
})
```

Use `tags` for named tags or `tag: "order-confirmation"` for a single tag. Use string values for metadata. Lettermint includes metadata in webhook payloads so you can connect delivery events to application records. See [tags](/platform/emails/tags) for tag options.

### Route and delivery settings

```elixir
Lettermint.Email.send(client, %{
  from: "notifications@yourdomain.com",
  to: ["recipient@example.com"],
  subject: "Your account is ready",
  text: "You can now sign in.",
  route: "transactional",
  settings: %{track_opens: false, track_clicks: true, tls: "enforced"}
})
```

`route` selects a route by its slug. The tracking settings control open and click tracking for this email. `tls: "enforced"` requires TLS for email delivery. See [tracking](/platform/emails/tracking/introduction) and [TLS](/platform/emails/tls).

### Idempotency

Pass an idempotency key as a request option:

```elixir
Lettermint.Email.send(
  client,
  %{
    from: "orders@yourdomain.com",
    to: ["customer@example.com"],
    subject: "Order 12345 confirmed",
    text: "Your order is confirmed."
  },
  idempotency_key: "order-confirmation-12345"
)
```

Use the same key when you retry the same email. The SDK does not retry requests automatically. See [idempotency](/platform/emails/idempotency) for the API rules and retention window.

### File attachments

Read the file and encode its content as Base64:

```elixir
content = "/path/to/invoice.pdf" |> File.read!() |> Base.encode64()

Lettermint.Email.send(client, %{
  from: "invoices@yourdomain.com",
  to: ["customer@example.com"],
  subject: "Your invoice",
  text: "Your invoice is attached.",
  attachments: [
    %{filename: "invoice.pdf", content: content, content_type: "application/pdf"}
  ]
})
```

For an inline image, set `content_id` on the attachment and use the matching `cid:` reference in the HTML body. See [inline images](/platform/emails/inline-images).

### Scheduled emails

Use a future timestamp with an explicit time zone:

```elixir
scheduled_at = DateTime.utc_now() |> DateTime.add(3600, :second) |> DateTime.to_iso8601()

Lettermint.Email.send(client, %{
  from: "reminders@yourdomain.com",
  to: ["recipient@example.com"],
  subject: "Appointment reminder",
  text: "Your appointment starts in one hour.",
  scheduled_at: scheduled_at
})
```

The SDK also provides `Lettermint.Messages.reschedule/3` and `Lettermint.Messages.cancel/2`. See [email scheduling](/platform/emails/scheduling) for scheduling limits and cancellation behavior.

## 4. Use the pipe operator

Use `Lettermint.EmailBuilder` to build an email in separate steps:

```elixir
alias Lettermint.EmailBuilder, as: Email

client
|> Email.new()
|> Email.from("orders@yourdomain.com")
|> Email.to(["customer@example.com"])
|> Email.subject("Order 12345 confirmed")
|> Email.html("<h1>Order confirmed</h1><p>Thank you for your order.</p>")
|> Email.text("Order confirmed. Thank you for your order.")
|> Email.tags([%{name: "category", value: "order-confirmation"}])
|> Email.metadata(%{order_id: "12345"})
|> Email.send(idempotency_key: "order-confirmation-12345")
```

Each builder function replaces one field and returns a new builder. Use `Email.to_map/1` to prepare an item for `Lettermint.Email.send_batch/2`.

## 5. Responses and errors

Successful JSON responses use generated structs. For an email send, read `message_id` and `status`. API acceptance does not confirm delivery. Use [webhooks](/platform/webhooks/introduction) for delivery and bounce events.

Handle API errors and other SDK failures with a `case` expression:

```elixir
payload = %{
  from: "hello@yourdomain.com",
  to: ["recipient@example.com"],
  subject: "Hello",
  text: "This is a test email."
}

case Lettermint.Email.send(client, payload) do
  {:ok, message} ->
    IO.puts("Email ID: #{message.message_id}")

  {:error, %Lettermint.Error{kind: :api, status: status}} ->
    IO.puts(:stderr, "Lettermint rejected the request: HTTP #{status}.")

  {:error, %Lettermint.Error{kind: kind}} ->
    IO.puts(:stderr, "The request failed: #{kind}.")
end
```

Error kinds are `:api`, `:transport`, and `:decode`. API errors include the HTTP status and a `body` field. The SDK removes token text from error data. Invalid local arguments raise `ArgumentError`.

To set the request timeout, pass milliseconds when you create the client:

```elixir
client = Lettermint.email(System.fetch_env!("LETTERMINT_PROJECT_TOKEN"), timeout: 15_000)
```

The timeout applies separately to connection setup and response receipt.

## Team API

Use a separate Team API token to manage team resources. Set it in `LETTERMINT_TEAM_TOKEN`:

```elixir
api = Lettermint.api(System.fetch_env!("LETTERMINT_TEAM_TOKEN"))
{:ok, page} = Lettermint.Domains.list(api, query: %{"page[size]" => 25})

Enum.each(page.data, fn domain -> IO.puts(domain.domain) end)
```

Do not use a Team API token with the email client. See the [Team API quickstart](/platform/teams/team-api/quickstart) for token setup and permissions.

## Track delivery, opens, and bounces

Enable [tracking](/platform/emails/tracking/introduction) on your route to record opens and clicks. Subscribe to [webhooks](/platform/webhooks/introduction) for delivery and bounce events.

Use `Lettermint.Webhook.verify(raw_body, signature_header, webhook_secret)` to verify webhook signatures. Pass the original request body before JSON decoding. Verification returns `:ok` or `{:error, :invalid_signature}`. See [webhook signing](/platform/webhooks/signing) for the header and signing secret.

## Use Lettermint with Swoosh

If your application uses Swoosh, use its [Lettermint adapter](https://hexdocs.pm/swoosh/Swoosh.Adapters.Lettermint.html) for mail delivery. The official SDK provides direct Sending API and Team API access. The Swoosh adapter remains a separate integration.

## Next steps

<CardGroup cols={2}>
    <Card title="Tags" icon="tag" href="/platform/emails/tags">
        Organize emails with tags.
    </Card>
    <Card title="Webhooks" icon="webhook" href="/platform/webhooks/introduction">
        Receive delivery and bounce events.
    </Card>
    <Card title="Test addresses" icon="envelope" href="/platform/emails/sending-test-emails">
        Test delivery and bounce handling.
    </Card>
    <Card title="Hex package" icon="box" href="https://hex.pm/packages/lettermint">
        Find package releases and dependencies.
    </Card>
</CardGroup>

<Card title="GitHub repository" icon="github" href="https://github.com/lettermint/lettermint-elixir">
    Read the source code or report an issue.
</Card>
