---
title: SDKs
slug: sdks
docTags: 
createdAt: 2026-08-03T04:00:00.000Z
---

Official, typed clients for the v1 API in four languages. Each one covers the
full public surface — single verification, batch jobs, ESP integrations and
keyword rules, usage reports — and handles auth, retries, and idempotency for
you.

| Language             | Package                                                                          | Requires    |
| -------------------- | -------------------------------------------------------------------------------- | ----------- |
| Node.js / TypeScript | [`@mailfloss/sdk`](https://www.npmjs.com/package/@mailfloss/sdk)                 | Node.js 18+ |
| Python               | [`mailfloss`](https://pypi.org/project/mailfloss/)                               | Python 3.9+ |
| Ruby                 | [`mailfloss`](https://rubygems.org/gems/mailfloss)                               | Ruby 2.6+   |
| Go                   | [`github.com/mailfloss/mailfloss-go`](https://github.com/mailfloss/mailfloss-go) | Go 1.24+    |

All four are MIT-licensed and zero-dependency (standard library only), retry `429`/`5xx`
responses honoring `Retry-After`, and attach an `Idempotency-Key` to every
POST so a retried batch submit never double-charges.

You don't need an SDK — every endpoint is plain HTTP + JSON, as shown in the
[Quickstart](quickstart). The SDKs just save you the plumbing.

## Authentication

Every client sends your key as `Authorization: Bearer <key>` (see
[Authentication](authentication)). Create a key in the dashboard under
**Developers → API keys**.

The recommended setup is the `MAILFLOSS_API_KEY` environment variable — each
client reads it when constructed with no arguments, which keeps the key out of
your source tree:

```bash
export MAILFLOSS_API_KEY="mf_live_your_key_here"
```

You can also pass the key explicitly (shown per language below). If no key is
found, the constructor raises a configuration error rather than failing later
on the first request.

## Install and verify an email

### Node.js / TypeScript

```bash
npm install @mailfloss/sdk
```

```typescript
import { Mailfloss } from "@mailfloss/sdk";

// Reads MAILFLOSS_API_KEY, or pass it: new Mailfloss({ apiKey: "mf_live_..." })
const mailfloss = new Mailfloss();

const result = await mailfloss.verify({ email: "jane@acme.com" });

console.log(result.status);     // "passed" | "undeliverable" | "risky" | "unknown"
console.log(result.passed);     // true when safe to send
console.log(result.reason);     // e.g. "available"
console.log(result.suggestion); // typo fix, when one is detected
```

### Python

```bash
pip install mailfloss
```

```python
from mailfloss import Mailfloss

# Reads MAILFLOSS_API_KEY, or pass it: Mailfloss(api_key="mf_live_...")
client = Mailfloss()

result = client.verify("jane@acme.com")

print(result["status"])   # "passed" | "undeliverable" | "risky" | "unknown"
print(result["passed"])   # True when safe to send
print(result["reason"])   # e.g. "available"
if result.get("suggestion"):
    print("Did you mean:", result["suggestion"])
```

### Ruby

```shell
gem install mailfloss
```

```ruby
require "mailfloss"

# Reads MAILFLOSS_API_KEY, or pass it: Mailfloss::Client.new(api_key: "mf_live_...")
client = Mailfloss::Client.new

result = client.verify(email: "jane@acme.com")

result[:status]     # "passed" | "undeliverable" | "risky" | "unknown"
result[:passed]     # true when safe to send
result[:reason]     # e.g. "available"
result[:suggestion] # typo fix, when one is detected
```

Responses are parsed with **symbol keys**.

### Go

```bash
go get github.com/mailfloss/mailfloss-go
```

```go
package main

import (
	"context"
	"fmt"
	"log"

	mailfloss "github.com/mailfloss/mailfloss-go"
)

func main() {
	// Reads MAILFLOSS_API_KEY, or pass it: mailfloss.New(mailfloss.WithAPIKey("mf_live_..."))
	client, err := mailfloss.New()
	if err != nil {
		log.Fatal(err)
	}

	res, err := client.Verify.Check(context.Background(), mailfloss.VerifyParams{
		Email: "jane@acme.com",
	})
	if err != nil {
		log.Fatal(err)
	}

	// Status is one of: passed, undeliverable, risky, unknown
	fmt.Printf("%s -> %s (%s) passed=%t\n", res.Email, res.Status, res.Reason, res.Passed)
}
```

## Batch verification

Submit a job, poll its status, then page through results — the same flow as the
[Quickstart](quickstart), one method per step. Pass an optional `webhook_url`
to be notified on completion instead of polling.

### Node.js / TypeScript

```typescript
const { id } = await mailfloss.batchVerify.create({
  emails: ["jane@acme.com", "bob@example.com"],
  webhook_url: "https://example.com/hooks/mailfloss", // optional; omit to poll
});

const { status, progress } = await mailfloss.batchVerify.status(id);

const page = await mailfloss.batchVerify.results(id, { per_page: 100 });
console.log(page.results);
```

### Python

```python
job = client.batch_verify.create(
    emails=["jane@acme.com", "bob@example.com"],
    webhook_url="https://example.com/hooks/mailfloss",  # optional; omit to poll
)

status = client.batch_verify.status(job["id"])
print(status["status"], status.get("progress"))

page = client.batch_verify.results(job["id"], per_page=500)
for row in page.get("results", []):
    print(row)
```

### Ruby

```ruby
batch = client.batch_verify.create(
  emails: ["jane@acme.com", "bob@example.com"],
  webhook_url: "https://example.com/hooks/mailfloss" # optional; omit to poll
)

status = client.batch_verify.status(batch[:id])
puts "#{status[:status]} #{status[:progress]}"

page = client.batch_verify.results(batch[:id], per_page: 100)
page[:results]
```

### Go

```go
job, err := client.BatchVerify.Create(ctx, mailfloss.BatchVerifyCreateParams{
	Emails:     []string{"jane@acme.com", "bob@example.com"},
	WebhookURL: "https://example.com/hooks/mailfloss", // optional; omit to poll
})

status, err := client.BatchVerify.Status(ctx, job.ID)
fmt.Printf("job %s: %s (%.0f%%)\n", job.ID, status.Status, status.Progress)

page, err := client.BatchVerify.Results(ctx, job.ID, mailfloss.BatchVerifyResultsParams{
	PerPage: 1000,
})
for _, r := range page.Results {
	fmt.Printf("%s -> %s (%s)\n", r.Email, r.Status, r.Reason)
}
```

## Errors

Non-2xx responses surface as a language-native error carrying the same fields
as the JSON envelope documented in [Errors](errors) — `status`, `code`,
`message`, `type`, and `request_id`. Branch on the stable `code`, never on
`message`.

| Language             | Error type                                                                                                 |
| -------------------- | ---------------------------------------------------------------------------------------------------------- |
| Node.js / TypeScript | `MailflossError` (config problems: `MailflossConfigError`)                                                 |
| Python               | `MailflossError` (config problems: `MailflossConfigError`)                                                 |
| Ruby                 | `Mailfloss::APIError` (config problems: `Mailfloss::ConfigurationError`; both subclass `Mailfloss::Error`) |
| Go                   | `*mailfloss.Error`, matched with `errors.As`                                                               |

```python
from mailfloss import Mailfloss, MailflossError

try:
    client.jobs.get("does-not-exist")
except MailflossError as err:
    print(err.status)      # 404
    print(err.code)        # stable machine-readable code
    print(err.request_id)  # quote this to support
```

## Pagination

List endpoints return a `{ data, pagination }` envelope. Pass
`pagination.next_cursor` back as the `cursor` parameter to fetch the next page
— see [Pagination](pagination) for the full model.

## Next steps

- **API reference** — every endpoint, parameter, and response shape.
- **Connect an ESP** — verify-first automation across 18 email platforms.
- **Webhooks** — get notified instead of polling.
