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.
1. Create a connect link (curl)
Send your user to the returned url. It works once, for 24 hours. After they finish, an accountConnected webhook fires and the line appears in the dashboard.
curl -X POST https://inboxwire.buffersoltech.com/v1/connect-links \
-H "Authorization: Bearer $INBOXWIRE_KEY" \
-H "Content-Type: application/json" \
-d '{"externalId":"user_42","redirectUrl":"https://app.example.com/connected"}'2. List messages (curl)
q accepts one syntax on every provider: from:, to:, subject:, after:, before:, has:attachment and plain words.
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.
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.
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, queuedGo
Standard library only.
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.
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.
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.
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.