Jmap (JMAP v0.0.3)

View Source

Basic JMAP client for Elixir.

This module provides a high-level interface for interacting with JMAP email servers. It handles all the complexity of fetching emails, downloading blobs, and sanitizing content.

It was purpose built for fetching a single email at a time from FastMail. However, it may serve for others as a useful starting point for building a more feature rich client.

Configuration

In order to use JMAP, you'll need to configure a JMAP provider and token.

config :jmap,
  provider: "fastmail",
  api_token: "your_api_token"

HTTP Client

The library uses Erlang's built-in :httpc module by default. If you want to use the more feature-rich req package instead, simply add it as a dependency:

defp deps do
  [
    {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0"}
  ]
end

The library will automatically use req if it's available, falling back to :httpc if it's not.

Creating a client

Once you have a provider and token, you can create a client.

{:ok, client} = Jmap.new()

This returns an authenticated client struct or fails if the provider or token are invalid.

Getting email

{:ok, email} = Jmap.get_next_mail(client)
#=> %Jmap.Email{
  id: "Md45jz",
  subject: "Meeting Tomorrow",
  from: [
    %Jmap.Email.EmailAddress{
      name: "John Smith",
      email: "john.smith@example.com"
    }
  ],
  to: [
    %Jmap.Email.EmailAddress{
      name: "Jane Doe",
      email: "jane.doe@example.com"
    }
  ],
  received_at: "2024-03-20T15:30:00Z",
  text_body: [
    %Jmap.Email.EmailBody{
      blob_id: "blob_123",
      type: "text/plain",
      charset: "utf-8",
      part_id: "part1",
      size: 423,
      contents: "Hi Jane,\n\nJust confirming our meeting tomorrow at 2pm.\n\nBest regards,\nJohn"
    },
    %Jmap.Email.EmailBody{
      blob_id: "blob_124",
      type: "text/plain",
      charset: "utf-8",
      part_id: "part2",
      size: 256,
      contents: "> On Thu, Mar 21, 2024 at 10:00 AM Jane Doe <jane@example.com> wrote:\n> > Hi John,\n> > \n> > Let's meet tomorrow at 2pm."
    }
  ],
  html_body: [
    %Jmap.Email.EmailBody{
      blob_id: "blob_125",
      type: "text/html",
      charset: "utf-8",
      part_id: "part3",
      size: 628,
      contents: "<div><p>Hi Jane,</p><p>Just confirming our meeting tomorrow at 2pm.</p><p>Best regards,<br>John</p></div>"
    }
  ],
  attachments: [
    %Jmap.Email.Attachment{
      blob_id: "blob_126",
      type: "application/pdf",
      name: "agenda.pdf",
      size: 125_840,
      disposition: "attachment",
      contents: <<...>>  # Binary content
    }
  ],
  thread_id: "thread_789",
  original_quote: %Jmap.Email.EmailBody{
    blob_id: "blob_124",
    type: "text/plain",
    charset: "utf-8",
    part_id: "part2",
    size: 256,
    contents: "> On Thu, Mar 21, 2024 at 10:00 AM Jane Doe <jane@example.com> wrote:\n> > Hi John,\n> > \n> > Let's meet tomorrow at 2pm."
  }
}

This returns the next email from the inbox. By default, it will return the oldest email first. You can pass a limit and offset to paginate through the emails.

{:ok, email} = Jmap.get_next_mail(client, limit: 10, offset: 0)

You can also change the default sort order by passing a sort option.

{:ok, email} = Jmap.get_next_mail(client,
  sort: [%{"isAscending" => false, "property" => "receivedAt"}])

Get an email by ID.

This works exactly like Jmap.get_next_mail/1 but it will return an email by ID.

{:ok, email} = Jmap.get_email(client, "email_id")

This returns an email by ID.

Summary

Functions

archive_email(client, email_id)

See Jmap.Client.archive_email/2.

fetch_blob(client, blob_id)

See Jmap.Client.fetch_blob/2.

fetch_email(client, email_id)

See Jmap.Client.fetch_email/2.

fetch_emails(client, options \\ [])

See Jmap.Client.fetch_emails/2.

get_email(client, email_id)

Fetches an email by its ID, including its full contents.

This function handles all the complexity of:

  • Fetching the email metadata
  • Downloading the email body contents
  • Downloading attachment contents
  • Sanitizing HTML content
  • Inlining embedded images
  • Converting the data into a structured format

Parameters

  • client: The JMAP client struct
  • email_id: The ID of the email to fetch

Examples

iex> Jmap.get_email(client, "email123")
{:ok, %Jmap.Email{
  subject: "Hello",
  text_body: [
    %{contents: "Hello world", type: "text/plain"},
    %{contents: "> Previous message", type: "text/plain"}
  ],
  html_body: [
    %{contents: "<p>Hello world</p>", type: "text/html"}
  ],
  original_quote: %{
    contents: "> On Thu, Mar 21, 2024 at 10:00 AM John Doe <john@example.com> wrote:

Hi there,

This is the original message.",

    type: "text/plain"
  }
}}
iex> Jmap.get_email(client, "email123")
{:error, "Email not found"}

get_next_mail(client)

Fetches the oldest email from the inbox, including its full contents.

This function handles all the complexity of:

  • Fetching the email metadata
  • Downloading the email body contents
  • Downloading attachment contents
  • Sanitizing HTML content
  • Inlining embedded images
  • Converting the data into a structured format

Parameters

  • client: The JMAP client struct

Examples

iex> Jmap.get_next_mail(client)
{:ok, %Jmap.Email{
  subject: "Hello",
  text_body: [
    %{contents: "Hello world", type: "text/plain"},
    %{contents: "> Previous message", type: "text/plain"}
  ],
  html_body: [
    %{contents: "<p>Hello world</p>", type: "text/html"}
  ],
  original_quote: %{
    contents: "> On Thu, Mar 21, 2024 at 10:00 AM John Doe <john@example.com> wrote:

Hi there,

This is the original message.",

    type: "text/plain"
  }
}}
iex> Jmap.get_next_mail(client)
{:error, "Inbox empty"}

new()

See Jmap.Client.new/0.

new(api_token, provider, options \\ [])

See Jmap.Client.new/3.