unsent
unsent.dev
Get Started

Elixir

Official Unsent Elixir SDK for sending emails and managing contacts.

This guide shows how to install and use the official unsent Elixir SDK.

Installation

First, you need to add the unsent package to your project's dependencies. Open your mix.exs file and add the following line to the deps function:

def deps do
  [
    {:unsent, "~> 1.0"}
  ]
end

After adding the dependency, run the following command in your terminal to fetch and install the package:

mix deps.get

Initialize

To start using the SDK, you need to create a client instance. You'll need your API key, which you can find in your Unsent dashboard.

Create a new client using Unsent.new/1:

client = Unsent.new("un_xxx")

Send an email

Now you're ready to send your first email. You can do this by defining an email map and passing it to the Emails.send/2 function.

Here is a simple example of sending an HTML email:

alias Unsent.Emails

# Initialize the client
client = Unsent.new("un_xxx")

# Define the email parameters
email = %{
  to: "user@example.com",
  from: "no-reply@yourdomain.com",
  subject: "Welcome",
  html: "<strong>Hello!</strong>"
}

# Send the email
case Emails.send(client, email) do
  {:ok, data} ->
    IO.puts("Email sent! ID: #{data["id"]}")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

With attachments and scheduling

You can also send emails with attachments and schedule them for a later time.

  • Attachments: Add an attachments list where each item has a filename and content (Base64 encoded string).
  • Scheduling: Use the scheduledAt field with an ISO 8601 formatted date string.
email = %{
  to: "user@example.com",
  from: "no-reply@yourdomain.com",
  subject: "Report",
  text: "See attached.",
  attachments: [
    %{
      filename: "report.txt",
      content: "SGVsbG8gd29ybGQ=" # Content must be Base64 encoded
    }
  ],
  # Schedule for 10 minutes from now
  scheduledAt: DateTime.utc_now() |> DateTime.add(600, :second) |> DateTime.to_iso8601()
}

{:ok, data} = Emails.create(client, email)

Batch send

If you need to send multiple emails at once, use the Emails.batch/2 function. This is more efficient than sending them one by one.

emails = [
  %{
    to: "a@example.com",
    from: "no-reply@yourdomain.com",
    subject: "A",
    html: "<p>A</p>"
  },
  %{
    to: "b@example.com",
    from: "no-reply@yourdomain.com",
    subject: "B",
    html: "<p>B</p>"
  }
]

case Emails.batch(client, emails) do
  {:ok, data} ->
    IO.puts("Sent #{length(data["data"])} emails")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

Idempotent Retries

To safely retry requests, you can pass custom headers using the opts keyword list.

# For single email
Unsent.Emails.send(client, email, headers: [{"Idempotency-Key", "unique-key-123"}])

# For batch emails
Unsent.Emails.batch(client, emails, headers: [{"Idempotency-Key", "batch-unique-key-123"}])

Retrieve and manage emails

You can retrieve the status of sent emails, update their scheduled time, or cancel them if they haven't been sent yet.

Get an email

To check the status of a specific email, use Emails.get/2 with the email ID:

case Emails.get(client, "email_123") do
  {:ok, email} ->
    IO.puts("Email status: #{email["status"]}")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

Update schedule time

If you need to change when a scheduled email will be sent, you can update its scheduledAt property:

update = %{
  scheduledAt: DateTime.utc_now() |> DateTime.add(3600, :second) |> DateTime.to_iso8601()
}

{:ok, data} = Emails.update(client, "email_123", update)

Cancel a scheduled email

To stop a scheduled email from being sent, use the Emails.cancel/2 function:

case Emails.cancel(client, "email_123") do
  {:ok, _data} ->
    IO.puts("Email cancelled successfully")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

Contacts

Manage your audience by creating and updating contacts. Contacts can be organized into "contact books" (identified by book_id).

Create a contact

Add a new contact to a specific contact book. You can include metadata like their subscription plan.

alias Unsent.Contacts

contact = %{
  email: "user@example.com",
  firstName: "Jane",
  metadata: %{
    plan: "pro"
  }
}

{:ok, data} = Contacts.create(client, "book_123", contact)

Get a contact

Retrieve details about a specific contact using their ID and the book ID:

{:ok, contact} = Contacts.get(client, "book_123", "contact_456")

Update a contact

Update a contact's information, such as their name or metadata:

update = %{
  firstName: "John",
  metadata: %{
    plan: "enterprise"
  }
}

{:ok, data} = Contacts.update(client, "book_123", "contact_456", update)

Upsert a contact

Use upsert to create a contact if they don't exist, or update them if they do. This is useful for syncing data.

contact = %{
  email: "user@example.com",
  firstName: "Jane"
}

{:ok, data} = Contacts.upsert(client, "book_123", "contact_456", contact)

Delete a contact

Remove a contact from a book:

{:ok, data} = Contacts.delete(client, "book_123", "contact_456")

Campaigns

Campaigns allow you to send emails to an entire contact book.

Create a campaign

Create a new campaign by specifying the content and the target contact book ID.

alias Unsent.Campaigns

campaign = %{
  name: "Welcome Series",
  subject: "Welcome!",
  html: "<p>Thanks for joining us!</p>",
  from: "welcome@yourdomain.com",
  contactBookId: "book_123"
}

{:ok, data} = Campaigns.create(client, campaign)
IO.puts("Campaign created! ID: #{data["id"]}")

Schedule a campaign

Once created, you can schedule the campaign to be sent at a specific time.

schedule = %{
  scheduledAt: "2024-12-01T10:00:00Z"
}

{:ok, data} = Campaigns.schedule(client, "campaign_123", schedule)

Pause and resume campaigns

You can pause a running or scheduled campaign and resume it later.

# Pause
{:ok, _data} = Campaigns.pause(client, "campaign_123")
IO.puts("Campaign paused successfully!")

# Resume
{:ok, _data} = Campaigns.resume(client, "campaign_123")
IO.puts("Campaign resumed successfully!")

Domains

Manage the domains you use to send emails.

List domains

Retrieve a list of all your domains and their verification status.

alias Unsent.Domains

case Domains.list(client) do
  {:ok, domains} ->
    Enum.each(domains, fn domain ->
      IO.puts("Domain: #{domain["domain"]}, Status: #{domain["status"]}")
    end)
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

Create a domain

Add a new domain to your account.

domain = %{
  domain: "yourdomain.com"
}

{:ok, data} = Domains.create(client, domain)

Verify a domain

Check the verification status of a domain.

case Domains.verify(client, 123) do
  {:ok, data} ->
    IO.puts("Verification status: #{data["status"]}")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end

Error handling

The SDK supports two modes of error handling.

Raising exceptions (Default)

By default, the SDK raises an Unsent.HTTPError exception if the API returns a non-2xx response. This is useful if you want to let the process crash or handle errors with try/rescue.

try do
  {:ok, data} = Emails.get(client, "email_123")
rescue
  e in Unsent.HTTPError ->
    IO.puts("HTTP #{e.status_code}: #{e.error["message"]}")
end

Returning error tuples

If you prefer to handle errors as return values (e.g., {:error, reason}), you can set raise_on_error: false when initializing the client.

client = Unsent.new("un_xxx", raise_on_error: false)

case Emails.get(client, "email_123") do
  {:ok, data} ->
    IO.puts("Success!")
  {:error, error} ->
    IO.puts("Error: #{error["message"]}")
end