Skip to content
Preview: outbound-sdk is not on npm yet and its repository is private. Instantly and HeyReach are beta, Smartlead is unverified.What is verified
outbound-sdk
Esc
↑↓navigate↵open⌘Jpreview
On this page

Adapters

What an adapter is, the four that ship, what each can and cannot do in one table checked against the code, and how much of each vendor's API is covered.

An adapter is the part that knows one vendor. It describes each request and maps the response to the SDK’s records. It never retries, sleeps or walks pages: createOutbound does that around it, the same way for every vendor. You pass one adapter to createOutbound and call the same methods whichever vendor is behind it (see usage).

Each vendor adapter has two layers: normalized reads that mean the same thing on every vendor, and a typed native catalog of the vendor’s own reads for everything else.

The adapters

Adapter Import Channel Key variable Page
Instantly (API V2) outbound-sdk/instantly email INSTANTLY_API_KEY Instantly
Smartlead outbound-sdk/smartlead email SMARTLEAD_API_KEY Smartlead
HeyReach outbound-sdk/heyreach LinkedIn HEYREACH_API_KEY HeyReach
In-memory outbound-sdk/memory both none In-memory

combine from outbound-sdk/combine puts several of them, or several keys for one of them, behind one instance.

Every vendor factory takes the same core options:

Option Default What it is
apiKey the key variable above The vendor key. A missing key throws when the adapter is constructed.
workspaceId <PROVIDER>_WORKSPACE_ID, then default Your own label for the client or workspace. It is part of every ref (provider:workspace:id).
baseUrl the vendor’s API For a proxy or a test server.
capabilities what the adapter declares Overrides single capabilities, for example to turn one off on a plan without the endpoint.

Constructing an adapter makes no request.

How far each is verified

One line each, from what is verified, which is the page to read before relying on a number:

  • Instantly is beta: its reads were run, read-only, against one live workspace on 2026-11-14.
  • HeyReach is beta: its reads were run, read-only, against one live workspace on 2026-11-14.
  • Smartlead is unverified: written from Smartlead’s public documentation and tested offline only.
  • In-memory is stable: it has no vendor behind it.

outbound.maturity reports this at run time, and outbound.caveats lists what the adapter assumes and has not confirmed. Each adapter’s page shows its caveats in full.

What each adapter can do

A filter is server (the vendor filters), client (the SDK filters after fetching and marks the result derived) or no. A stat is exact, derived or no. A call that needs a no is refused as Unsupported before any request leaves. The repository’s test suite checks this table against the adapters’ own declarations (packages/outbound-sdk/test/adapters-doc.test.ts). See capabilities for what each key gates and how to branch on it.

Capability Instantly Smartlead HeyReach
blocklist yes yes yes
campaigns.settings yes yes no
campaigns.status server client server
deletionDetection no no no
events.parse yes yes yes
events.verify secret signature secret
leads.byCampaign yes yes yes
leads.engagement yes no no
leads.get yes yes yes
leads.list yes yes no
leads.lookup yes yes yes
leads.since client client server
leads.timeline yes yes yes
lists.leads yes no yes
lists.list yes yes yes
messages.body full full full
messages.byThread yes yes yes
messages.list yes yes yes
messages.since server client server
native yes yes yes
replies.category vendor vendor no
replies.list yes yes yes
replies.since server client server
senders.byCampaign yes yes yes
senders.get yes yes yes
senders.health yes yes yes
senders.list yes yes yes
senders.warmup yes yes no
sequence yes yes yes
sequence.subsequences yes yes no
stats.byDay exact exact exact
stats.bySender derived exact exact
stats.byStep exact exact no
stats.campaign exact exact exact
stats.workspace exact exact exact
tags yes yes no
webhooks.list yes yes yes
workspaces yes yes no

The in-memory adapter declares everything except native, events.parse, events.verify and deletionDetection.

What stays no, and why

What a vendor’s API cannot answer stays no. The SDK does not fill the gap with a guess.

  • HeyReach has no campaign settings, subsequences, tags or per-lead counts. Its API can set a campaign’s schedule but not read it, a campaign has one sequence tree, no endpoint lists the workspace’s tags, and no lead carries open or reply counts.
  • HeyReach has no per-step stats and no warmup: LinkedIn accounts are not warmed up.
  • HeyReach replies carry no category. Its auto-tags describe the lead, not a message; the newest one is the lead’s interest.
  • HeyReach has no list of every lead. Leads are read per campaign or per saved list, or found by profile or email.
  • HeyReach cannot list workspaces with a workspace key.
  • Smartlead cannot list the leads of a saved list, and its per-lead counts are documented only as a placeholder response.
  • Instantly’s per-mailbox stats are derived: its mailbox analytics exist per day only, so the adapter sums them.
  • No adapter reveals deleted records. The reconcile plugin compares what you stored with what the vendor still lists.

How much of each vendor’s API is covered

Every endpoint in each vendor’s API reference is listed on its coverage page with how the SDK reaches it: through a normalized read, through the native catalog, excluded with a reason, or a write the SDK does not make.

Vendor Endpoints Normalized Native Excluded Writes
Instantly 268 22 105 4 137
Smartlead 230 27 78 20 105
HeyReach 106 17 25 3 61

A read is excluded only when it returns secrets (API keys, mailbox passwords, proxy credentials), duplicates another endpoint’s data exactly, reports nothing about campaigns, leads, senders or results (billing, marketplace pricing, search filter lists), or returns a file the SDK cannot carry. Reads that spend vendor credits are listed as writes.

Rate limits each adapter declares

Adapter Declared limit Stricter lanes
Instantly 100 per second and 6,000 per minute, per workspace GET /emails: 20 per minute
Smartlead 60 per minute (the limit of a client key; an admin key is documented at 200) three endpoints at 10 per minute
HeyReach 15 per 2 seconds (seen live, not documented) and 300 per minute the organization API: 300 per minute

The SDK spends share (default 0.5) of these numbers, because the vendor’s limit is shared with the customer’s other tools. Rate limits explains the budget, and what is verified says which of these numbers were measured.

The provider catalog and choosing an adapter at run time

outbound-sdk/providers lists every provider with its channels, its maturity and the environment variables it reads, without importing any adapter. outbound-sdk/loader builds the adapter that OUTBOUND_SDK_PROVIDER names, reading that adapter’s own key from the environment; the CLI picks its adapter this way.

import { createOutbound } from "outbound-sdk";
import { fromEnv, loadableProviders } from "outbound-sdk/loader";
import { providers } from "outbound-sdk/providers";

// The catalog imports no adapter, so it is safe in a config screen.
for (const provider of providers) {
  const required = provider.envVars
    .filter((variable) => variable.required)
    .map((variable) => variable.name);

  console.log(provider.id, provider.maturity, provider.channels, required);
}

// fromEnv() builds the adapter OUTBOUND_SDK_PROVIDER names, or the one you
// pass. Only that adapter's code is loaded.
console.log(loadableProviders());

const outbound = createOutbound({
  adapter: await fromEnv({ provider: "memory" }),
});

const campaigns = await outbound.campaigns.list();

console.log(outbound.provider, campaigns.items.length);
instantly beta [ 'email' ] [ 'INSTANTLY_API_KEY' ]
smartlead unverified [ 'email' ] [ 'SMARTLEAD_API_KEY' ]
heyreach beta [ 'linkedin' ] [ 'HEYREACH_API_KEY' ]
memory stable [ 'email', 'linkedin' ] []
[ 'heyreach', 'instantly', 'memory', 'smartlead' ]
memory 2

Writing an adapter

The adapter contract is the Adapter type exported from outbound-sdk. The checklist for adding one is in the repository, in docs/new-adapter.md, with the research note each existing adapter was written from under docs/research/.

Was this page helpful?