Skip to content
Inboxwireby BufferSol

Docs

Quickstart by language

Connect a mailbox, list its messages, send with an idempotency key and verify a webhook, in curl, TypeScript, Python and Go.

Before you start

You need an API key (Keys in the dashboard, scope read to list and search, send to send, admin to create connect links and webhooks) and, for Gmail or Microsoft 365, your own OAuth client saved under OAuth clients. IMAP mailboxes need no client.

Every example uses https://inboxwire.buffersoltech.com/v1. Self-hosting? Use your own URL instead. Add an x-environment header (development, staging or production) to pick an environment; production is the default.

2. List messages (curl)

q accepts one syntax on every provider: from:, to:, subject:, after:, before:, has:attachment and plain words.

bash
curl -G https://inboxwire.buffersoltech.com/v1/accounts/$ACCOUNT_ID/messages \
  -H "Authorization: Bearer $INBOXWIRE_KEY" \
  --data-urlencode "q=from:billing@example.com has:attachment" \
  --data-urlencode "limit=20"

TypeScript

The SDK is generated from the OpenAPI document (packages/sdk in the repository, @buffersol/inboxwire-sdk once published). Methods are typed from the same schemas the server validates with.

ts
import { createClient } from "@buffersol/inboxwire-sdk";

const inboxwire = createClient({ apiKey: process.env.INBOXWIRE_KEY! });

const { items } = await inboxwire.listAccounts();
const page = await inboxwire.listMessages({ id: items[0].id, query: { q: "after:2026-11-01", limit: 20 } });
console.log(page.items.map((m) => m.subject));

// Safe to retry: the same key returns the same outbox item for 7 days.
await inboxwire.sendMessage({
  id: items[0].id,
  idempotencyKey: "invoice-1042",
  body: { to: [{ address: "ana@example.com" }], subject: "Your invoice", text: "Attached." },
});

Python

No SDK needed; the API is plain JSON over HTTPS.

python
import os, requests

API = "https://inboxwire.buffersoltech.com/v1"
H = {"Authorization": f"Bearer {os.environ['INBOXWIRE_KEY']}"}

accounts = requests.get(f"{API}/accounts", headers=H, timeout=30).json()["items"]
r = requests.get(f"{API}/accounts/{accounts[0]['id']}/messages", headers=H, params={"q": "from:billing@example.com", "limit": 20}, timeout=30)
r.raise_for_status()
for m in r.json()["items"]:
    print(m["subject"])

requests.post(
    f"{API}/accounts/{accounts[0]['id']}/messages/send",
    headers={**H, "Idempotency-Key": "invoice-1042"},
    json={"to": [{"address": "ana@example.com"}], "subject": "Your invoice", "text": "Attached."},
    timeout=30,
).raise_for_status()  # 202, queued

Go

Standard library only.

go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://inboxwire.buffersoltech.com/v1/accounts", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("INBOXWIRE_KEY"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var out struct {
		Items []struct{ ID, Email, State string } `json:"items"`
	}
	json.NewDecoder(res.Body).Decode(&out)
	for _, a := range out.Items {
		fmt.Println(a.Email, a.State)
	}
}

Verify a webhook: Node

Verify the signature on the raw body before you parse it. The header is Inboxwire-Signature: t=<unix>,v1=<hex>, the HMAC-SHA256 of t.rawBody with your endpoint secret. Reject anything older than five minutes.

ts
import { verifyWebhook } from "@buffersol/inboxwire-sdk";

export async function POST(req: Request) {
  const raw = await req.text();
  const ok = await verifyWebhook(process.env.INBOXWIRE_WEBHOOK_SECRET!, raw, req.headers.get("inboxwire-signature"));
  if (!ok) return new Response("bad signature", { status: 400 });
  const event = JSON.parse(raw); // { id, type, created, data }
  // Dedupe on event.id: retries and replays keep the same id.
  return new Response("ok");
}

Verify a webhook: Python

Use a constant-time compare.

python
import hashlib, hmac, time

def verify(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > tolerance:
        return False
    mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts["v1"])

Verify a webhook: Go

Read the body once, verify, then decode.

go
func verify(secret string, body []byte, header string) bool {
	var t int64
	var v1 string
	for _, p := range strings.Split(header, ",") {
		kv := strings.SplitN(p, "=", 2)
		if len(kv) != 2 { continue }
		if kv[0] == "t" { t, _ = strconv.ParseInt(kv[1], 10, 64) }
		if kv[0] == "v1" { v1 = kv[1] }
	}
	if d := time.Now().Unix() - t; d > 300 || d < -300 { return false }
	mac := hmac.New(sha256.New, []byte(secret))
	fmt.Fprintf(mac, "%d.", t)
	mac.Write(body)
	got, err := hex.DecodeString(v1)
	return err == nil && hmac.Equal(mac.Sum(nil), got)
}

Errors, usage and deleting a mailbox

Errors are JSON: {"error": "code"}. 402 mailbox_limit means the plan's mailbox allowance is full (Developer and Cloud Starter); Cloud Growth bills extra mailboxes instead. GET /v1/usage returns used, included and extra mailboxes.

DELETE /v1/accounts/{id} (admin scope) revokes the Google token, deletes the account, its credentials and queued sends, and returns a deletionRecordId you can keep as evidence.