Official libraries & CLI

Typed Dairo clients for nine languages, plus a CLI for the terminal and a hosted MCP server for AI agents.

Dairo ships typed, idiomatic clients for nine languages, plus a CLI for your terminal and an MCP server for AI agents. They all line up: the methods, fields, and IDs you learn in one carry over to the rest.

Choose an interface

SDKs

Typed, idiomatic clients for your application code. Start here when you're writing a service or agent in a supported language.

CLI

Scripting, CI, and quick manual operations from any shell.

MCP server

Native tool access for Claude, Cursor, and other MCP-capable agents.

JavaScript / TypeScript

npm install dairo
import { Dairo } from "dairo";

const dairo = new Dairo({ apiKey: process.env.DAIRO_API_KEY! });

const result = await dairo.messages.send({
  inboxId: "inbox_123",
  to: "ada@example.com",
  subject: "Hello from Dairo",
  text: "Hi Ada",
});
console.log(result.id, result.status, result.channel);

The JavaScript SDK takes apiKey explicitly so browser and edge runtimes stay predictable; pass a custom baseUrl or fetch for testing.

Python

pip install dairo
from dairo import Dairo

dairo = Dairo()  # reads DAIRO_API_KEY, or pass api_key="dairo_live_…"

message = dairo.messages.send(
    inbox_id="inbox_123",
    to="customer@example.com",
    subject="Welcome",
    text="Thanks for trying Dairo.",
)
print(message.id, message.status)

Requires Python 3.10 or later.

Go

go get github.com/dairo-app/dairo-go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    dairo "github.com/dairo-app/dairo-go"
)

func main() {
    client := dairo.NewClient(os.Getenv("DAIRO_API_KEY"))
    out, err := client.Send(context.Background(), dairo.SendMessageRequest{
        InboxID: "inbox_123",
        To:      []string{"person@example.com"},
        Subject: "Welcome",
        Text:    "Hello from Dairo",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(out.ID, out.Status)
}

Ruby

gem install dairo --pre
require "dairo"

client = Dairo::Client.new(api_key: ENV.fetch("DAIRO_API_KEY"))

response = client.send({
  inboxId: "inbox_123",
  to: ["person@example.com"],
  subject: "Hello from Dairo",
  text: "Plain text body",
})

puts response.fetch("status") # "sent"

PHP

composer require dairo/dairo
<?php

use Dairo\DairoClient;

$dairo = new DairoClient(getenv('DAIRO_API_KEY'));

$outbound = $dairo->send([
    'inboxId' => 'inbox_123',
    'to' => ['person@example.com'],
    'subject' => 'Welcome',
    'text' => 'Hello from Dairo',
]);

echo $outbound['status']; // sent

Java

<dependency>
  <groupId>app.dairo</groupId>
  <artifactId>dairo-java</artifactId>
  <version>0.1.2</version>
</dependency>
import app.dairo.DairoClient;

DairoClient client = new DairoClient(System.getenv("DAIRO_API_KEY"));

String response = client.send(
    "{\"inboxId\":\"inbox_123\",\"to\":[\"person@example.com\"]," +
    "\"subject\":\"Hello\",\"text\":\"Plain body\"}",
    "idem-123"); // optional idempotency key

The Java SDK returns raw JSON strings so your code stays forward-compatible, and ships typed shapes such as SendWarning and OutboundEventData that document the contract.

Rust

Cargo.toml
[dependencies]
dairo = { git = "https://github.com/dairo-app/dairo-rust" }
use dairo::{Client, RequestOptions, SendMessageRequest};

#[tokio::main]
async fn main() -> Result<(), dairo::DairoError> {
    let client = Client::new("dairo_live_xxx")?;
    let sent = client.send(
        &SendMessageRequest {
            inbox_id: "inbox_123".to_string(),
            to: vec!["person@example.com".to_string()],
            subject: Some("Welcome".to_string()),
            text: Some("Hello from Dairo".to_string()),
            // channel defaults to the inbox's channel ("email"); set "a2a" to target Dairo inboxes.
            ..Default::default()
        },
        RequestOptions::default(),
    ).await?;
    println!("message {} is {}", sent.id, sent.status);
    Ok(())
}

The library is separate from the CLI, which ships as its own binary.

.NET

dotnet add package Dairo
using Dairo;

var client = new DairoClient(new DairoOptions { ApiKey = "dairo_live_xxx" });

var sent = await client.SendAsync(new SendMessageRequest(
    InboxId: "inbox_123",
    To: ["person@example.com"],
    Subject: "Hello",
    Text: "Plain text body"));

Console.WriteLine($"{sent.Id} {sent.Status}");

Elixir

mix.exs
def deps do
  [
    {:dairo, "~> 0.1"}
  ]
end
Elixir
client = Dairo.Client.new(System.fetch_env!("DAIRO_API_KEY"))

{:ok, response} =
  Dairo.Client.send(client, %{
    inboxId: "inbox_123",
    to: ["person@example.com"],
    subject: "Hello",
    text: "Plain-text body"
  })

response["status"]

Requires Elixir 1.18+ on Erlang/OTP 27+.

The same in every language

Whatever client you pick, these things work identically — learn them once.

  • One authentication model. Every client takes the same dairo_live_… API key; most examples read it from DAIRO_API_KEY.
  • One resource model. Each area of the API maps to a namespace — messages (send, list, get, cancel, events), threads, inboxes, domains, webhooks, apiKeys, templates, audiences, letters, and more — snake_case in Python, PascalCase methods in .NET.
  • Stable IDs. messageId, inboxId, threadId, and event IDs are identical across every SDK, the REST API, the CLI, and webhook payloads.

Cross-cutting behaviors — the response envelope, idempotency, scopes, send warnings, and one-time secrets — are the same everywhere and documented once in Foundations.

On this page