@builtbyecho/reverbin
v0.1.3
Published
Node.js ESM SDK for Reverbin agent inboxes, threads, messages, approvals, webhooks, credentials, billing, and account lifecycle APIs.
Maintainers
Readme
@builtbyecho/reverbin
Zero-dependency Node.js ESM SDK for the Reverbin API: agent inboxes, threads, messages, approvals, signed webhooks, API keys, billing, and account lifecycle operations.
Install
Install the public Node.js package from npm:
npm install @builtbyecho/reverbinThe SDK requires Node.js 20 or newer, uses Node's built-in fetch, and supports ESM imports only. Browser runtimes are not supported; never put a Reverbin API key in client-side code.
Authenticated client
import { ReverbinClient } from '@builtbyecho/reverbin';
const apiKey = process.env.REVERBIN_API_KEY;
const inboxId = process.env.REVERBIN_INBOX_ID;
if (!apiKey || !inboxId) throw new Error('REVERBIN_API_KEY and REVERBIN_INBOX_ID are required');
const reverbin = new ReverbinClient({
baseUrl: process.env.REVERBIN_BASE_URL ?? 'https://api.reverbin.com',
apiKey,
timeoutMs: 30_000,
});
const threads = await reverbin.inboxes.threads(inboxId);
const latest = threads.data[0];
if (latest) {
await reverbin.threads.reply(latest.id, {
text: 'Received — I am handling this from the agent workflow.',
});
}Signup already creates the first inbox. Use the returned REVERBIN_INBOX_ID; call reverbin.inboxes.create only when the workflow needs another inbox and the account has quota.
Self-serve signup
The public signup method does not require an API key. It does require a stable caller-owned idempotency key:
import { randomUUID } from 'node:crypto';
import { ReverbinClient } from '@builtbyecho/reverbin';
const reverbin = new ReverbinClient();
const result = await reverbin.signups.create({
idempotency_key: randomUUID(),
requester_email: '[email protected]',
agent_name: 'Support Agent',
agent_use_case: 'Handle customer support replies and escalate unusual requests.',
preferred_inbox_name: 'support-agent',
});
if (result.credentials_returned) {
// Store result.api_key.token now; it is returned once.
console.log(result.inbox.email_address);
} else {
// Safe replay: identifiers and a recovery message, but no credentials.
console.log(result.message);
}Keep the original idempotency key with the request outcome. Reuse it only with the identical request body after a lost response.
Core method groups
| Group | Methods |
| --- | --- |
| signups | create |
| inboxes | create, list, get, threads |
| messages | compose, list |
| threads | get, messages, reply, forward |
| approvals | list, approve, reject |
| webhooks | create, list, deliveries, rotateSecret, revoke |
| apiKeys | create, list, rotate, revoke |
| billing | plans, checkout, portal |
| account | export, requestDeletion, cancelDeletion |
| auditLogs | list |
| signupRequests | create, list, update for legacy operator-assisted workflows |
See https://reverbin.com/docs/api for request and response contracts.
Pagination
Collection methods return { data, next_cursor, has_more }. Pass next_cursor back unchanged as cursor to the same method and parent resource:
const first = await reverbin.inboxes.list({ limit: 25 });
const second = first.has_more
? await reverbin.inboxes.list({ limit: 25, cursor: first.next_cursor })
: null;Cursors are opaque and tenant-, collection-, and parent-bound.
Timeouts, aborts, and errors
Requests time out after 30 seconds by default. Configure timeoutMs on the client or per request, and pass a caller AbortSignal in method request options. The client does not retry requests automatically.
import {
ReverbinApiError,
ReverbinClient,
ReverbinResponseError,
ReverbinTimeoutError,
} from '@builtbyecho/reverbin';
const controller = new AbortController();
try {
await reverbin.inboxes.list(undefined, {
signal: controller.signal,
timeoutMs: 5_000,
});
} catch (error) {
if (error instanceof ReverbinApiError) {
console.error(error.status, error.code, error.requestId, error.retryAfterSeconds, error.quota);
} else if (error instanceof ReverbinTimeoutError) {
console.error('Timed out after', error.timeoutMs);
} else if (error instanceof ReverbinResponseError) {
console.error('Malformed API response', error.status, error.requestId);
}
}API error details are recursively credential-redacted. Even so, do not log one-time signup, API-key, or webhook-secret responses.
Webhook signatures
Register only a real reachable HTTPS endpoint, then store the returned secret immediately:
const webhookUrl = process.env.REVERBIN_WEBHOOK_URL;
if (!webhookUrl) throw new Error('REVERBIN_WEBHOOK_URL is required');
const webhook = await reverbin.webhooks.create({
url: webhookUrl,
events: ['email.received', 'email.sent', 'email.failed'],
});
// Store webhook.secret now; it is returned once.Verify the signature against the exact raw request bytes before parsing or acting on a webhook:
import { verifyWebhookSignature } from '@builtbyecho/reverbin/webhook-signatures';
const valid = verifyWebhookSignature(rawBody, signatureHeader, webhookSecret);During credential-rotation grace, verify x-echo-email-signature-previous separately with the previous secret. The stable x-echo-email-* header prefix is retained for wire compatibility.
After signature verification succeeds, atomically claim the unique x-echo-email-delivery value before performing side effects, using a durable processing lease and completed state. Commit transactional business changes and the completed state together. For external side effects, enqueue a transactional outbox operation keyed by the delivery ID. A completed claim returns success without re-executing; an expired processing lease may be retried. This deduplicates concurrent and repeated deliveries without losing retryability.
Retry and idempotency boundaries
The SDK never retries automatically.
- Signup, Stripe Checkout/Portal, API-key rotation, and webhook-secret rotation require explicit
idempotency_keyinputs. Reuse the same key only with the identical body after a lost response. - Do not blindly retry compose, reply, forward, approval decisions, inbox creation, webhook creation/revocation, API-key creation/revocation, or account mutations after an ambiguous response. Inspect resource, thread, audit, or delivery state first.
- Signup replay prevents duplicate provisioning but intentionally omits one-time credentials.
Launch limitations
Outbound attachments are not supported in the launch SDK. Compose, reply, and forward accept text plus optional HTML. Inbound attachments remain available in the authenticated human mail console.
