MillionSend Docs

SDKs

Official MillionSend SDKs for nine languages, published under MIT.

Every SDK mirrors the shape of the corresponding official Resend SDK, so migrating is mostly a find-and-replace: swap the import/class and point the base URL at MillionSend. All SDKs are MIT-licensed and live in their own repositories under the MillionSend GitHub org.

Since the API is Resend wire-compatible, the official Resend SDKs also work — point them at MillionSend via their base-URL option (e.g. RESEND_BASE_URL). From 0.9.0, every MillionSend SDK also covers received mail and inboxes.

Base URL

api.millionsend.com — what every example on this page uses, and the default from SDK 0.5.0: with no base URL configured, an SDK talks to Cloud.

Node.js / TypeScript

millionsend on npm — Node 18+.

npm install millionsend
import { MillionSend } from "millionsend";

const ms = new MillionSend("ms_123", { baseUrl: "https://api.millionsend.com" });

const { data, error } = await ms.emails.send({
  from: "Acme <onboarding@acme.dev>",
  to: "success@simulator.amazonses.com",
  subject: "Hello from MillionSend",
  html: "<strong>It works!</strong>",
});

Python

millionsend on PyPI — Python 3.9+.

pip install millionsend
import millionsend

millionsend.api_key = "ms_123"
millionsend.base_url = "https://api.millionsend.com"

email = millionsend.Emails.send({
    "from": "Acme <onboarding@acme.dev>",
    "to": "success@simulator.amazonses.com",
    "subject": "Hello from MillionSend",
    "html": "<strong>It works!</strong>",
})

Go

millionsend-go — Go 1.21+.

go get github.com/MillionSend/millionsend-go
client := millionsend.NewClient("ms_123")
client.BaseURL = "https://api.millionsend.com"

sent, err := client.Emails.Send(&millionsend.SendEmailRequest{
    From:    "Acme <onboarding@acme.dev>",
    To:      []string{"success@simulator.amazonses.com"},
    Subject: "Hello from MillionSend",
    Html:    "<strong>It works!</strong>",
})

Ruby

millionsend on RubyGems — Ruby 3.0+.

gem install millionsend
require "millionsend"

Millionsend.api_key  = "ms_123"
Millionsend.base_url = "https://api.millionsend.com"

email = Millionsend::Emails.send(
  from: "Acme <onboarding@acme.dev>",
  to: "success@simulator.amazonses.com",
  subject: "Hello from MillionSend",
  html: "<strong>It works!</strong>"
)

PHP

millionsend/millionsend-php on Packagist — PHP 8.1+.

composer require millionsend/millionsend-php
use MillionSend\MillionSend;

$ms = MillionSend::client('ms_123', 'https://api.millionsend.com');

$email = $ms->emails->send([
    'from' => 'Acme <onboarding@acme.dev>',
    'to' => 'success@simulator.amazonses.com',
    'subject' => 'Hello from MillionSend',
    'html' => '<strong>It works!</strong>',
]);

Rust

millionsend on crates.io — async (tokio + reqwest).

[dependencies]
millionsend = "0.9"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
use millionsend::{MillionSend, SendEmailOptions};

let ms = MillionSend::with_base_url("ms_123", "https://api.millionsend.com");

let sent = ms.emails.send(&SendEmailOptions {
    from: "Acme <onboarding@acme.dev>".into(),
    to: "success@simulator.amazonses.com".into(),
    subject: "Hello from MillionSend".into(),
    html: Some("<strong>It works!</strong>".into()),
    ..Default::default()
}).await?;

Java

com.millionsend:millionsend-java on Maven Central — Java 11+.

<dependency>
  <groupId>com.millionsend</groupId>
  <artifactId>millionsend-java</artifactId>
  <version>0.9.0</version>
</dependency>
MillionSend ms = new MillionSend("ms_123", "https://api.millionsend.com");

CreateEmailResponse sent = ms.emails().send(
    CreateEmailOptions.builder()
        .from("Acme <onboarding@acme.dev>")
        .to("success@simulator.amazonses.com")
        .subject("Hello from MillionSend")
        .html("<strong>It works!</strong>")
        .build());

.NET

MillionSend on NuGet — targets net8.0.

dotnet add package MillionSend
using MillionSend;

var client = new MillionSendClient("ms_123", "https://api.millionsend.com");

var res = await client.EmailSendAsync(new EmailMessage
{
    From = "Acme <onboarding@acme.dev>",
    To = "success@simulator.amazonses.com",
    Subject = "Hello from MillionSend",
    Html = "<strong>It works!</strong>",
});

Elixir

millionsend on Hex — Elixir 1.15+.

# mix.exs
def deps do
  [{:millionsend, "~> 0.9"}]
end
config :millionsend, MillionSend.Client,
  api_key: System.get_env("MILLIONSEND_API_KEY"),
  base_url: "https://api.millionsend.com"

{:ok, email} =
  MillionSend.Emails.send(%{
    from: "Acme <onboarding@acme.dev>",
    to: "success@simulator.amazonses.com",
    subject: "Hello from MillionSend",
    html: "<strong>It works!</strong>"
  })

Receiving and inboxes

From 0.9.0, every SDK reads received mail and works with inboxes, with the same tree in each language's idiom (the names below are Node's):

ResourceMethods
emails.receivinglist (with wait, since, inboxId, to, from, subjectContains, unread, includeQuarantined), get, reply, forward, remove
emails.receiving.attachmentslist, get
inboxescreate, list, get (by id or address), update, remove
inboxes.safetyget, update
inboxes.codeslatest (with from, wait, maxAge)
inboxes.quarantinelist, release (with trustSender)
inboxes.labelslist, create, update, remove
inboxes.threadslist, search, get, update, remove
inboxes.threads.emailslist, get, reply, forward
inboxes.draftslist, create, get, update, remove, send, reject
import { MillionSend } from "millionsend";

const ms = new MillionSend("ms_123");

// Wait up to 30 seconds for the next email delivered to an inbox.
const { data: page } = await ms.emails.receiving.list({ inboxId: "7eeb9517-aed7-40d7-ad65-500be42ed147", wait: 30 });

// The newest verification code or sign-in link it received.
const { data: code } = await ms.inboxes.codes.latest("7eeb9517-aed7-40d7-ad65-500be42ed147", { wait: 30 });
  • Reply, forward and draft sends take an idempotency key (in Node { idempotencyKey }, in Go WithIdempotencyKey, and each language's equivalent). Pass one to make a retry safe: the SDK sends none on its own.
  • A reply or forward the inbox's rules hold answers a draft instead of the sent email, and the SDKs say which: in Node the result carries kind: "sent" or kind: "draft", and each language has its equivalent.
  • Unknown fields and enum values are kept or ignored, never an error, so a newer API doesn't break an older SDK.
  • Domains gain receiving_catch_all, API keys inbox_id, webhooks inbox_ids, and the webhook event types email.received and the inbox events.

On this page