agentsclimarketplace

Instantly local dev loop

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/instantly-pack/skills/instantly-local-dev-loop

'Configure Instantly.ai local development with mock server and test workflows.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill instantly-local-dev-loop

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

6.7 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it

Instantly Local Dev Loop

Overview

Set up a local development workflow for Instantly integrations. Instantly provides a mock server at `` for testing without sending real emails or consuming API limits. This skill covers mock server usage, integration testing, and local webhook development.

Prerequisites

  • Completed instantly-install-auth setup
  • Node.js 18+ with TypeScript
  • A separate Instantly API key for dev/test (recommended)

Instructions

Step 1: Configure Dev Environment

// src/config.ts
import "dotenv/config";

interface Config {
  baseUrl: string;
  apiKey: string;
  useMock: boolean;
}

export function getConfig(): Config {
  const useMock = process.env.INSTANTLY_USE_MOCK === "true";
  return {
    baseUrl: useMock
      ? "https://developer.instantly.ai/_mock/api/v2"
      : process.env.INSTANTLY_BASE_URL || "https://api.instantly.ai/api/v2",
    apiKey: process.env.INSTANTLY_API_KEY || "",
    useMock,
  };
}
# .env.development
INSTANTLY_API_KEY=your-dev-api-key
INSTANTLY_BASE_URL=https://api.instantly.ai/api/v2
INSTANTLY_USE_MOCK=true

# .env.production
INSTANTLY_API_KEY=your-prod-api-key
INSTANTLY_BASE_URL=https://api.instantly.ai/api/v2
INSTANTLY_USE_MOCK=false

Step 2: Build a Testable API Client

// src/instantly.ts
import { getConfig } from "./config";

const config = getConfig();

export async function instantly<T = unknown>(
  path: string,
  options: RequestInit = {}
): Promise<T> {
  const url = `${config.baseUrl}${path}`;
  const res = await fetch(url, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${config.apiKey}`,
      ...options.headers,
    },
  });

  if (res.status === 429) {
    const retryAfter = parseInt(res.headers.get("retry-after") || "2", 10);
    console.warn(`Rate limited. Retrying in ${retryAfter}s...`);
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
    return instantly<T>(path, options);
  }

  if (!res.ok) {
    const body = await res.text();
    throw new InstantlyError(res.status, path, body);
  }

  return res.json() as Promise<T>;
}

class InstantlyError extends Error {
  constructor(
    public status: number,
    public path: string,
    public body: string
  ) {
    super(`Instantly API ${status} on ${path}: ${body}`);
    this.name = "InstantlyError";
  }
}

Step 3: Write Integration Tests

// tests/instantly.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { instantly } from "../src/instantly";

describe("Instantly API Integration", () => {
  it("should list campaigns", async () => {
    const campaigns = await instantly<Array<{ id: string; name: string }>>(
      "/campaigns?limit=5"
    );
    expect(Array.isArray(campaigns)).toBe(true);
  });

  it("should list email accounts", async () => {
    const accounts = await instantly<Array<{ email: string }>>(
      "/accounts?limit=5"
    );
    expect(Array.isArray(accounts)).toBe(true);
  });

  it("should create and delete a lead list", async () => {
    const list = await instantly<{ id: string; name: string }>(
      "/lead-lists",
      {
        method: "POST",
        body: JSON.stringify({ name: `test-list-${Date.now()}` }),
      }
    );
    expect(list.id).toBeDefined();
    expect(list.name).toContain("test-list-");

    // Clean up
    await instantly(`/lead-lists/${list.id}`, { method: "DELETE" });
  });

  it("should handle 401 on bad key", async () => {
    const res = await fetch("https://api.instantly.ai/api/v2/campaigns?limit=1", {
      headers: { Authorization: "Bearer invalid-key" },
    });
    expect(res.status).toBe(401);
  });
});

Step 4: Local Webhook Testing with ngrok

set -euo pipefail
# Start your webhook server locally
# In terminal 1:
npx tsx src/webhook-server.ts  # listens on port 3000

# In terminal 2 — expose with ngrok:
ngrok http 3000

# Register the ngrok URL as a webhook
curl -X POST https://api.instantly.ai/api/v2/webhooks \
  -H "Authorization: Bearer $INSTANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Local Dev Webhook",
    "target_hook_url": "https://abc123.ngrok.io/webhooks/instantly",
    "event_type": "all_events"
  }'
// src/webhook-server.ts — minimal local webhook receiver
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/instantly", (req, res) => {
  console.log("Webhook received:", JSON.stringify(req.body, null, 2));
  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log("Webhook server on http://localhost:3000"));

Step 5: Test Webhook Delivery

// After registering the webhook, test it via API
async function testWebhook(webhookId: string) {
  await instantly(`/webhooks/${webhookId}/test`, { method: "POST" });
  console.log("Test webhook fired — check your local server logs");
}

Project Structure

instantly-integration/
├── src/
│   ├── config.ts           # Environment-aware config
│   ├── instantly.ts         # API client with retry
│   └── webhook-server.ts   # Local webhook receiver
├── tests/
│   └── instantly.test.ts   # Integration tests
├── .env.development         # Dev config (mock mode)
├── .env.production          # Prod config
├── package.json
└── tsconfig.json

Error Handling

ErrorCauseSolution
Mock returns unexpected dataMock schema mismatchCheck mock docs at developer.instantly.ai
ECONNREFUSED on localhostWebhook server not runningStart it before registering webhook
Tests passing locally, failing in CIDifferent env varsEnsure CI uses .env.development
ngrok tunnel expiredFree tier 2-hour limitRestart ngrok or upgrade

Resources

Next Steps

For production SDK patterns, see instantly-sdk-patterns.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.