Skip to content

Repository files navigation

Email Funnel AI — Node.js / TypeScript SDK

Official Node.js and TypeScript client for the Email Funnel AI integration API. Connect with your project key and secret, then call typed, resource-oriented methods — no manual URLs, headers, or JSON.

  • Node 18+ (uses the built-in fetch) — also works in Deno, Bun, and modern browsers
  • Zero runtime dependencies
  • First-class TypeScript types, dual ESM + CommonJS builds
  • Fluent, discoverable API with full endpoint coverage

Installation

npm install @emailfunnelai/sdk-node

Quick start

import { EmailFunnelAi } from '@emailfunnelai/sdk-node';

const client = new EmailFunnelAi({
    projectKey: 'pk_your_project_key',
    projectSecret: 'sk_your_secret_key',
});
// baseUrl defaults to https://app.emailfunnel.ai — pass it only for staging or local dev

// 1. Confirm your credentials
await client.validate();

// 2. Get or create a list
const list = await client.lists().create('Newsletter signups');

// 3. Sync a contact into it
await client
    .contacts()
    .sync({ email: 'jane@example.com', first_name: 'Jane', source_type: 'custom_saas' })
    .toList(list.id as string);

CommonJS works too:

const { EmailFunnelAi } = require('@emailfunnelai/sdk-node');

Usage

Every group is reached from the client and reads as resource → verb.

Contacts

// Single contact
await client.contacts().sync({ email: 'jane@example.com', source_type: 'app' }).toList(listId);

// Bulk (up to 500; the server queues automatically above 100 rows)
await client
    .contacts()
    .bulk(
        [
            { email: 'a@example.com', source_type: 'import' },
            { email: 'b@example.com', source_type: 'import' },
        ],
        'import',
    )
    .toList(listId);

// Real-time webhook event
await client.contacts().webhook('user.created', { email: 'jane@example.com' }).toList(listId);

Lists

await client.lists().all();
await client.lists().create('My leads', 'Optional description');
await client.lists().find(listId);

Bindings

await client.bindings().all();
await client.bindings().create('custom_crm', listId, { sync_enabled: true });
await client.bindings().find(bindingId);
await client.bindings().update(bindingId, { sync_enabled: false });
await client.bindings().delete(bindingId);
await client.bindings().status(bindingId, 'completed', 0);

Field mappings

await client.fieldMappings().config('custom_crm');
await client.fieldMappings().forBinding(bindingId).get();
await client.fieldMappings().forBinding(bindingId).update({ email: 'email', first_name: 'fname' });
await client.fieldMappings().forBinding(bindingId).reset();

Auto-tagging

await client.autoTagging().rules('custom_crm');
await client.autoTagging().preview('custom_crm', { email: 'jane@example.com', plan: 'enterprise' });

Analytics

await client.analytics().dashboard();
await client.analytics().heatmap({ range: 60, email_type: 'all' });
await client.analytics().funnels();
await client.analytics().campaigns();
await client.analytics().forms();

SSO

await client.sso().generate('owner@example.com'); // signed login URL
await client.sso().teamMembers();

Responses & errors

Successful calls resolve to the unwrapped data payload. Any error response rejects with a typed error:

import { ApiError, TransportError } from '@emailfunnelai/sdk-node';

try {
    await client.contacts().sync({ email: 'invalid' }).toList(listId);
} catch (error) {
    if (error instanceof ApiError) {
        error.status; // 422
        error.errorType; // "validation_error"
        error.messages; // { "contact.email": ["The email field is required."] }
        error.retryAfter; // set on 429 rate limits
    } else if (error instanceof TransportError) {
        // network / timeout failure (no HTTP response)
    }
}

Both extend EmailFunnelSdkError, so you can catch that to handle any SDK failure.

Branch on errorType for specific conditions — e.g. a contact list that has been deactivated rejects new members with a 409 list_inactive:

try {
    await client.contacts().sync(contact).toList(listId);
} catch (error) {
    if (error instanceof ApiError && error.errorType === 'list_inactive') {
        // The target list is inactive — reactivate it or pick another list.
    }
}

Common errorType values

errorType Status Meaning
invalid_credentials 401 Missing/invalid project key or secret
inactive_project 403 The connected project is inactive
validation_error 422 Request body failed validation (messages set)
invalid_email / suppressed 422 Email is undeliverable or suppressed
list_inactive 409 Target contact list is inactive and rejects new members
rate_limit_exceeded 429 1000 req/hour cap hit (retryAfter set)
sync_failed 500 Unexpected sync failure

Custom transport

The client uses the global fetch by default. Provide your own HttpClient to add logging, proxies, or a custom agent:

import { EmailFunnelAi, type HttpClient } from '@emailfunnelai/sdk-node';

const httpClient: HttpClient = {
    async send(method, url, headers, body) {
        // ...your transport...
        return { status: 200, body: '{"data":{}}' };
    },
};

const client = new EmailFunnelAi({ baseUrl, projectKey, projectSecret, httpClient });

Development

npm install
npm test        # vitest, no network
npm run build   # dual ESM + CJS + type declarations

Tests mock HTTP and never touch the network. A coverage test asserts the SDK exposes every documented endpoint.

License

MIT © Email Funnel AI

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages