npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@send-kit/sdk

v1.1.3

Published

Official SendKit SDK for sending emails and managing subscriptions

Readme

SendKit SDK

Official SDK for SendKit - Send beautiful emails with ease.

Installation

npm install @sendkit/sdk

Quick Start

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: "pk_your_public_key",
  secretKey: "sk_your_secret_key", // Optional for HMAC signing
});

// Subscribe a user
await sendkit.subscribe({
  email: "[email protected]",
  name: "John",
  surname: "Doe",
});

// Update contact information
await sendkit.updateContact({
  email: "[email protected]",
  firstName: "John",
  lastName: "Doe",
  marketingConsent: true,
  tags: ["vip", "newsletter"],
});

// Send a marketing email
const result = await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "My App", email: "[email protected]" },
  subject: "Welcome!",
  html: `
    <h1>Welcome to our platform</h1>
    <footer><a href="{{unsubscribe_link}}">Unsubscribe</a></footer>
  `,
  data: { firstName: "John" },
});

console.log("Email ID:", result.emailId); // Use emailId for tracking
console.log("Job ID:", result.jobId); // JobId for internal queue operations

Features

  • 🚀 Zero Configuration - No base URL required, automatically detects environment
  • 📧 Marketing Emails - Send beautiful newsletters and marketing campaigns
  • 💼 Transactional Emails - Send receipts, notifications, and important updates
  • 📰 Newsletter Management - Create and schedule newsletters to all project contacts
  • 👥 Contact Management - Update contact information and manage tags
  • 📋 Contact Listing - Get paginated lists of contacts with filtering and search
  • 📬 CC & BCC Support - Send carbon copies and blind carbon copies
  • ↩️ Reply-To Headers - Customize reply-to addresses for better customer engagement
  • 🎯 Flexible Sender - Send from any email on your verified domain
  • 🔒 Secure - HMAC signing for sensitive operations
  • 📊 Quota Management - Track and manage your email limits
  • 🚦 Rate Limiting - Built-in rate limiting for API endpoints
  • 📝 TypeScript - Full TypeScript support with type definitions
  • 📨 Newsletter Management - Subscribe and unsubscribe users with ease
  • 🔍 Email Tracking - Track email status with persistent email IDs
  • Email Cancellation - Cancel scheduled or queued emails before they're sent

API Reference

Constructor

new SendKit(config: SendKitConfig)

Parameters:

  • config.apiKey (string, required) - Your SendKit public API key
  • config.secretKey (string, optional) - Your SendKit secret key for HMAC signing

Methods

sendMarketingEmail(params)

Send a marketing email to a single recipient.

Important: Marketing emails must include the {{unsubscribe_link}} placeholder in your HTML content for compliance. Requests without this placeholder will be rejected.

const result = await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "My App", email: "[email protected]" },
  subject: "Welcome!",
  html: `
    <h1>Welcome {{firstName}}!</h1>
    <p>Great content here...</p>
    <footer>
      <a href="{{unsubscribe_link}}">Unsubscribe</a>
    </footer>
  `,
  previewText: "Welcome to our platform",
  replyTo: "[email protected]", // Optional
  cc: "[email protected]", // Optional (can be string or array)
  bcc: ["[email protected]", "[email protected]"], // Optional (can be string or array)
  data: { firstName: "John" },
  scheduledAt: new Date("2024-01-15T10:00:00Z"), // Optional
  newsletterId: "newsletter_123", // Optional
});

// Returns: { emailId: string, jobId: string, message: string }
console.log(result.emailId); // Persistent email ID for tracking
console.log(result.jobId); // BullMQ job ID for internal operations

sendTransactionalEmail(params)

Send a transactional email with optional attachments.

const result = await sendkit.sendTransactionalEmail({
  to: "[email protected]",
  from: { name: "My App", email: "[email protected]" },
  subject: "Order Confirmation",
  html: "<h1>Your order has been confirmed</h1>",
  previewText: "Order confirmation",
  replyTo: "[email protected]", // Optional
  cc: "[email protected]", // Optional (can be string or array)
  bcc: "[email protected]", // Optional (can be string or array)
  // Optional: schedule this transactional email to be sent in the future
  scheduledAt: new Date("2024-01-15T10:00:00Z"),
  attachments: [
    {
      filename: "receipt.pdf",
      content: Buffer.from("..."),
      contentType: "application/pdf",
    },
  ],
  headers: {
    "X-Custom-Header": "value",
  },
});

// Returns: { emailId: string, jobId: string, message: string }
console.log(result.emailId); // Persistent email ID for tracking

createNewsletter(params)

Create a newsletter with HTML content. This allows you to prepare a newsletter that can be scheduled to send to all your project contacts.

Important: Newsletter HTML must include the {{unsubscribe_link}} placeholder for compliance.

const newsletter = await sendkit.createNewsletter({
  title: "Weekly Newsletter - January 2024",
  subject: "Your Weekly Update",
  html: `
    <h1>Hello {{firstName}}!</h1>
    <p>Check out this week's highlights...</p>
    <footer>
      <a href="{{unsubscribe_link}}">Unsubscribe</a>
    </footer>
  `,
  previewLine: "Check out this week's highlights", // Optional
});

console.log(newsletter.newsletterId); // Use this ID to schedule the newsletter
console.log(newsletter.title);
console.log(newsletter.createdAt);

// Returns: { newsletterId: string, title: string, subject: string, previewLine: string | null, createdAt: Date, message: string }

scheduleNewsletter(params)

Schedule a newsletter to be sent at a specific date and time to all project contacts. Uses the same efficient infrastructure as the web dashboard.

// Step 1: Create a newsletter with content
const newsletter = await sendkit.createNewsletter({
  title: "Weekly Newsletter - January 2024",
  subject: "Your Weekly Update",
  html: `
    <h1>Hello {{firstName}}!</h1>
    <p>Great content here...</p>
    <footer><a href="{{unsubscribe_link}}">Unsubscribe</a></footer>
  `,
  previewLine: "Check out this week's highlights",
});

// Step 2: Schedule it for sending (sends to ALL project contacts automatically)
const result = await sendkit.scheduleNewsletter({
  newsletterId: newsletter.newsletterId,
  scheduledAt: new Date("2024-01-20T10:00:00Z"),
});

console.log(`✓ Scheduled for ${result.recipientCount} contacts`);
console.log(`Will send at: ${result.scheduledAt}`);

How it works:

  • ✅ Sends to all project contacts automatically
  • ✅ Pre-fetches all contact data in a single efficient batch query
  • ✅ Uses adaptive batch sizing based on real-time system performance
  • ✅ Automatically adjusts throughput for optimal delivery
  • ✅ Tracks completion via Redis coordination
  • ✅ Validates quota and subscription status
  • ✅ Ensures scheduled time is in the future

Returns: ScheduleNewsletterResponse

{
  success: true,
  message: "Newsletter scheduled for 2024-01-20T10:00:00.000Z",
  newsletterId: "newsletter_123",
  scheduledAt: "2024-01-20T10:00:00.000Z",
  recipientCount: 1250  // Number of contacts who will receive the newsletter
}

Email Tracking

The SDK provides persistent email IDs that allow you to track emails even after queue jobs complete. Unlike job IDs which are ephemeral and cleaned up after processing, email IDs persist permanently in the database.

getEmailStatus(emailId)

Get the current status and details of an email by its ID.

const status = await sendkit.getEmailStatus("email-id-here");

console.log(status.email.status); // QUEUED, SCHEDULED, SENT, DELIVERED, OPENED, etc.
console.log(status.email.messageId); // AWS SES Message ID (available after sent)
console.log(status.email.to); // Recipient email
console.log(status.email.subject); // Email subject
console.log(status.email.createdAt); // When email was created

Email Status Values:

  • QUEUED - Email is in the queue, waiting to be sent
  • SCHEDULED - Email is scheduled for future delivery
  • SENT - Email has been sent to AWS SES
  • DELIVERED - Email was successfully delivered
  • OPENED - Recipient opened the email
  • CLICKED - Recipient clicked a link in the email
  • BOUNCED - Email bounced (hard or soft bounce)
  • COMPLAINT - Recipient marked email as spam
  • CANCELLED - Email was cancelled before sending

Example: Poll for email status

// Send email
const result = await sendkit.sendTransactionalEmail({
  to: "[email protected]",
  from: { name: "MyApp", email: "[email protected]" },
  subject: "Order Confirmation",
  html: "<h1>Your order is confirmed!</h1>",
});

// Poll until email is sent
let status;
let attempts = 0;
const maxAttempts = 30;

while (attempts < maxAttempts) {
  status = await sendkit.getEmailStatus(result.emailId);

  if (status.email.status === "SENT" || status.email.status === "DELIVERED") {
    console.log(`Email sent! Message ID: ${status.email.messageId}`);
    break;
  }

  await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds
  attempts++;
}

cancelEmail(emailId)

Cancel a scheduled or queued email before it's sent.

// Schedule an email for later
const result = await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "Newsletter", email: "[email protected]" },
  subject: "Weekly Update",
  html: `
    <h1>This week's news</h1>
    <footer><a href="{{unsubscribe_link}}">Unsubscribe</a></footer>
  `,
  scheduledAt: new Date(Date.now() + 3600000), // 1 hour from now
});

// Cancel the scheduled email
const cancelled = await sendkit.cancelEmail(result.emailId);

if (cancelled.success) {
  console.log(cancelled.message); // "Email to [email protected] has been cancelled"
  console.log(cancelled.jobRemoved); // true if job was removed from queue
}

Notes:

  • Only emails with status QUEUED or SCHEDULED can be cancelled
  • Cancelled emails automatically refund your email quota
  • Attempting to cancel an already-sent email will return an error
  • The cancellation removes the job from the queue and updates the email status to CANCELLED

getQuota()

Get your current quota information.

const quota = await sendkit.getQuota();
console.log(`You have ${quota.emailsRemaining} emails remaining`);

subscribe(params)

Subscribe a user to a newsletter.

const result = await sendkit.subscribe({
  email: "[email protected]",
  name: "John", // optional
  surname: "Doe", // optional
});

if (result.success) {
  console.log(result.message); // "Successfully subscribed"
  console.log(result.verified); // true if HMAC signed, false otherwise
}

unsubscribe(params)

Unsubscribe a user from a newsletter.

const result = await sendkit.unsubscribe({
  email: "[email protected]",
});

if (result.success) {
  console.log(result.message); // "Successfully unsubscribed"
  console.log(result.verified); // true if HMAC signed, false otherwise
}

updateContact(params)

Update contact information using email as identifier.

const result = await sendkit.updateContact({
  email: "[email protected]",
  firstName: "John", // optional
  lastName: "Doe", // optional
  marketingConsent: true, // optional
  tags: ["vip", "newsletter"], // optional
});

if (result.success) {
  console.log(result.message); // "Contact updated successfully"
  console.log(result.contact.firstName); // "John"
  console.log(result.verified); // true if HMAC signed, false otherwise
}

getContacts(params)

Get a paginated list of contacts with optional filtering.

// Get first page with default limit
const result = await sendkit.getContacts();

if (result.success) {
  console.log(`Found ${result.contacts.length} contacts`);
  console.log(`Total pages: ${result.pagination.totalPages}`);
}

// Get specific page with custom limit and search
const searchResult = await sendkit.getContacts({
  page: 2,
  limit: 50,
  search: "john",
  marketingConsent: true,
});

// Filter by tags
const taggedContacts = await sendkit.getContacts({
  tags: ["vip", "newsletter"],
});

if (taggedContacts.success) {
  console.log(taggedContacts.contacts); // Array of Contact objects
  console.log(taggedContacts.pagination); // Pagination information
  console.log(taggedContacts.verified); // true if HMAC signed, false otherwise
}

Rate Limiting

SendKit implements rate limiting to ensure fair usage and system stability. The following limits apply per API key:

API Endpoint Limits

  • Marketing Emails (/api/send): 60 requests per minute
  • Transactional Emails (/api/send-transactional): 100 requests per minute
  • Newsletter Creation (/api/newsletter-create): 60 requests per minute
  • Newsletter Scheduling (/api/newsletter-schedule): 60 requests per minute
  • Email Status (/api/email-status): 60 requests per minute
  • Cancel Email (/api/cancel-email): 60 requests per minute
  • Quota Checking (/api/quota): 120 requests per minute
  • Newsletter Subscription (/api/subscribe): 120 requests per minute
  • Newsletter Unsubscription (/api/unsubscribe): 120 requests per minute

Rate Limit Headers

When rate limits are exceeded, the API returns a 429 Too Many Requests status with additional headers:

X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60

Handling Rate Limits

try {
  await sendkit.sendMarketingEmail(params);
} catch (error) {
  if (error.statusCode === 429) {
    const retryAfter = error.headers?.["retry-after"];
    console.log(`Rate limited. Retry after ${retryAfter} seconds`);
  }
}

Security

HMAC Signing

For additional security, you can sign your requests using your secret key:

const sendkit = new SendKit({
  apiKey: "pk_your_public_key",
  secretKey: "sk_your_secret_key",
});

When a secret key is provided, the SDK automatically signs all requests with an HMAC signature.

API Key Security

  • Never commit API keys to version control
  • Use environment variables
  • Rotate keys regularly
  • Use different keys for different environments

Sender Email Flexibility

SendKit allows you to send from any email address on your verified domain:

If your domain is verified:

If your domain is NOT verified:

  • ⚠️ Emails will be sent from [email protected] (SendKit's fallback)
  • 💡 The recipient will still see your project name
  • 🔐 This protects against domain spoofing

Example:

// If mycompany.com is verified, this will use [email protected]
await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "Support Team", email: "[email protected]" },
  subject: "We're here to help!",
  html: `<h1>Contact Support</h1><footer><a href="{{unsubscribe_link}}">Unsubscribe</a></footer>`,
});

// Different email, same domain - also works!
await sendkit.sendTransactionalEmail({
  to: "[email protected]",
  from: { name: "Sales Team", email: "[email protected]" },
  subject: "Special Offer",
  html: "<h1>Special offer just for you!</h1>",
});

Error Handling

The SDK throws custom error classes for different scenarios:

try {
  await sendkit.sendMarketingEmail(params);
} catch (error) {
  if (error instanceof ValidationError) {
    console.log("Invalid parameters:", error.message);
  } else if (error instanceof QuotaExceededError) {
    console.log("Email quota exceeded");
  } else if (error instanceof SendKitError) {
    console.log("API error:", error.statusCode, error.message);
  }
}

Examples

Basic Marketing Email

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
});

try {
  const result = await sendkit.sendMarketingEmail({
    to: "[email protected]",
    from: { name: "My Store", email: "[email protected]" },
    subject: "Welcome to My Store!",
    html: `
      <h1>Welcome to My Store!</h1>
      <p>We're excited to have you on board.</p>
      <a href="https://mystore.com">Visit our store</a>
      
      <!-- Required unsubscribe link for marketing emails -->
      <footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ccc; font-size: 12px; color: #666;">
        <a href="{{unsubscribe_link}}" style="color: #666;">Unsubscribe</a>
      </footer>
    `,
    previewText: "Welcome to My Store! We're excited to have you on board.",
    replyTo: "[email protected]",
    bcc: "[email protected]", // Track new customer signups
    data: {
      firstName: "John",
      storeUrl: "https://mystore.com",
    },
  });

  if (result.success) {
    console.log("Email sent!");
    console.log("Email ID:", result.emailId); // Use this to track the email
    console.log("Job ID:", result.jobId);
  }
} catch (error) {
  console.error("Failed to send email:", error.message);
}

Transactional Email with Attachment

import { SendKit } from "@sendkit/sdk";
import fs from "fs";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
  secretKey: process.env.SENDKIT_SECRET_KEY,
});

try {
  const receiptBuffer = fs.readFileSync("./receipt.pdf");

  const result = await sendkit.sendTransactionalEmail({
    to: "[email protected]",
    from: { name: "My Store", email: "[email protected]" },
    subject: "Order Confirmation #12345",
    html: `
      <h1>Thank you for your order!</h1>
      <p>Your order #12345 has been confirmed.</p>
      <p>Total: $99.99</p>
    `,
    previewText: "Your order #12345 has been confirmed. Total: $99.99",
    replyTo: "[email protected]",
    cc: "[email protected]", // Notify warehouse
    bcc: "[email protected]", // Track revenue
    attachments: [
      {
        filename: "receipt.pdf",
        content: receiptBuffer,
        contentType: "application/pdf",
      },
    ],
  });

  if (result.success) {
    console.log("Transactional email sent!");
    console.log("Email ID:", result.emailId); // Use this to track the email
    console.log("Job ID:", result.jobId);
  }
} catch (error) {
  console.error("Failed to send transactional email:", error.message);
}

Newsletter Campaign (All Project Contacts)

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
});

try {
  // Step 1: Create a newsletter with HTML content
  const newsletter = await sendkit.createNewsletter({
    title: "Weekly Newsletter - January 2024",
    subject: "Your Weekly Update",
    html: `
      <h1>Hello {{firstName}}!</h1>
      <p>Here's your weekly update.</p>
      <p>You're subscribed as: {{email}}</p>
      <a href="https://myapp.com">Read more</a>
      
      <!-- Required unsubscribe link -->
      <footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ccc; font-size: 12px; color: #666;">
        <a href="{{unsubscribe_link}}" style="color: #666;">Unsubscribe</a>
      </footer>
    `,
    previewLine: "Hello! Here's your weekly update.",
  });

  if (newsletter.success) {
    console.log(`✓ Newsletter created: ${newsletter.newsletterId}`);
  }

  // Step 2: Schedule it to send to all project contacts
  const result = await sendkit.scheduleNewsletter({
    newsletterId: newsletter.newsletterId,
    scheduledAt: new Date("2024-01-20T10:00:00Z"),
  });

  console.log(`✓ Scheduled for ${result.recipientCount} contacts`);
  console.log(`Will send at: ${result.scheduledAt}`);
} catch (error) {
  console.error("Failed to schedule newsletter:", error.message);
}

Note: The newsletter automatically sends to all contacts in your project. The system handles batching, optimization, and delivery tracking automatically.

Email Tracking and Management

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
});

// Send a scheduled email
const result = await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "Newsletter", email: "[email protected]" },
  subject: "Weekly Update",
  html: `
    <h1>This Week's Updates</h1>
    <p>Check out what's new...</p>
    <footer><a href="{{unsubscribe_link}}">Unsubscribe</a></footer>
  `,
  scheduledAt: new Date(Date.now() + 7200000), // 2 hours from now
});

console.log("Email scheduled!");
console.log("Email ID:", result.emailId); // Save this for tracking

// Later, check the status
const status = await sendkit.getEmailStatus(result.emailId);
console.log("Current status:", status.email.status); // "SCHEDULED"
console.log("Will be sent at:", status.email.createdAt);

// If needed, cancel before it's sent
if (status.email.status === "SCHEDULED") {
  const cancelled = await sendkit.cancelEmail(result.emailId);
  console.log(cancelled.message); // "Email to [email protected] has been cancelled"
}

// Poll for email delivery (useful for critical emails)
async function waitForEmailDelivery(emailId: string, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    const status = await sendkit.getEmailStatus(emailId);

    console.log(`[Attempt ${i + 1}] Status: ${status.email.status}`);

    if (status.email.status === "DELIVERED") {
      console.log("Email delivered successfully!");
      console.log("AWS Message ID:", status.email.messageId);
      return status;
    }

    if (
      status.email.status === "BOUNCED" ||
      status.email.status === "COMPLAINT"
    ) {
      console.error("Email failed:", status.email.status);
      return status;
    }

    // Wait 2 seconds before next check
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }

  throw new Error("Email delivery timeout");
}

// Use it for important transactional emails
const transactionalResult = await sendkit.sendTransactionalEmail({
  to: "[email protected]",
  from: { name: "MyApp", email: "[email protected]" },
  subject: "Payment Confirmation",
  html: "<h1>Payment received!</h1>",
});

try {
  await waitForEmailDelivery(transactionalResult.emailId);
  console.log("Payment confirmation delivered!");
} catch (error) {
  console.error("Failed to deliver payment confirmation:", error);
  // Implement fallback logic (retry, alert, etc.)
}

Check Quota

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
});

// Check quota
const quota = await sendkit.getQuota();

if (quota.success) {
  console.log(
    `Emails remaining: ${quota.emailsRemaining}/${quota.totalEmails}`
  );
  console.log(`Reset date: ${quota.resetDate}`);
}

Newsletter Management

import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
  secretKey: process.env.SENDKIT_SECRET_KEY, // Optional for HMAC signing
});

// Subscribe a user
const subscribeResult = await sendkit.subscribe({
  email: "[email protected]",
  name: "John",
  surname: "Doe",
});

if (subscribeResult.success) {
  console.log("Subscription result:", subscribeResult.message);
  console.log("HMAC verified:", subscribeResult.verified);
}

// Later, unsubscribe the same user
const unsubscribeResult = await sendkit.unsubscribe({
  email: "[email protected]",
});

if (unsubscribeResult.success) {
  console.log("Unsubscription result:", unsubscribeResult.message);
  console.log("HMAC verified:", unsubscribeResult.verified);
}

Custom Unsubscribe Pages

SendKit provides flexibility for handling unsubscribes. You have three options:

Option 1: Use SendKit's Hosted Unsubscribe Page (Default)

SendKit automatically includes unsubscribe links in your emails. Simply add this link to your email templates:

<a href="https://send-kit.com/unsubscribe/{{contact_id}}/{{newsletter_id}}">
  Unsubscribe
</a>

The {{contact_id}} and {{newsletter_id}} placeholders are automatically replaced by SendKit when sending emails. The unsubscribe page is pre-built, branded with your project logo and theme colors, and requires no setup.

Option 2: Build Your Own Unsubscribe Page

Create a custom unsubscribe experience on your own domain using the SDK:

// pages/unsubscribe.tsx or app/unsubscribe/route.ts
import { SendKit } from "@sendkit/sdk";

// Initialize SDK (server-side only)
const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
  secretKey: process.env.SENDKIT_SECRET_KEY,
});

// Handle unsubscribe request
export async function POST(request: Request) {
  const { email } = await request.json();

  try {
    const result = await sendkit.unsubscribe({ email });

    return Response.json({
      success: true,
      message: result.message,
      verified: result.verified,
    });
  } catch (error) {
    return Response.json(
      { success: false, error: error.message },
      { status: 400 }
    );
  }
}

Frontend Example (React):

"use client";
import { useState } from "react";

export default function UnsubscribePage() {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<
    "idle" | "loading" | "success" | "error"
  >("idle");
  const [message, setMessage] = useState("");

  const handleUnsubscribe = async (e: React.FormEvent) => {
    e.preventDefault();
    setStatus("loading");

    try {
      const response = await fetch("/api/unsubscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email }),
      });

      const data = await response.json();

      if (data.success) {
        setStatus("success");
        setMessage("You've been successfully unsubscribed.");
      } else {
        setStatus("error");
        setMessage(data.error || "Failed to unsubscribe.");
      }
    } catch (error) {
      setStatus("error");
      setMessage("An error occurred. Please try again.");
    }
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h1 className="text-2xl font-bold mb-4">Unsubscribe</h1>

      {status === "success" ? (
        <div className="bg-green-50 p-4 rounded">
          <p className="text-green-800">{message}</p>
        </div>
      ) : (
        <form onSubmit={handleUnsubscribe}>
          <p className="mb-4 text-gray-600">
            We're sorry to see you go. Enter your email to unsubscribe.
          </p>

          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="[email protected]"
            required
            className="w-full px-4 py-2 border rounded mb-4"
          />

          {status === "error" && <p className="text-red-600 mb-4">{message}</p>}

          <button
            type="submit"
            disabled={status === "loading"}
            className="w-full bg-red-600 text-white py-2 rounded hover:bg-red-700 disabled:opacity-50"
          >
            {status === "loading" ? "Unsubscribing..." : "Unsubscribe"}
          </button>
        </form>
      )}
    </div>
  );
}

Option 3: One-Click Unsubscribe (Advanced)

For the best user experience, implement one-click unsubscribe by capturing the contact ID from the URL:

// app/unsubscribe/[contactId]/route.ts
import { SendKit } from "@sendkit/sdk";

const sendkit = new SendKit({
  apiKey: process.env.SENDKIT_API_KEY,
  secretKey: process.env.SENDKIT_SECRET_KEY,
});

export async function GET(
  request: Request,
  { params }: { params: { contactId: string } }
) {
  try {
    // First, fetch the contact's email from your database using contactId
    const contact = await getContactById(params.contactId);

    if (!contact) {
      return new Response("Contact not found", { status: 404 });
    }

    // Unsubscribe the user
    const result = await sendkit.unsubscribe({ email: contact.email });

    // Return a nice HTML page
    return new Response(
      `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Unsubscribed</title>
          <style>
            body { font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px; text-align: center; }
            .success { color: #059669; font-size: 24px; margin-bottom: 16px; }
          </style>
        </head>
        <body>
          <div class="success">✓ Successfully Unsubscribed</div>
          <p>You will no longer receive emails from us at <strong>${contact.email}</strong></p>
        </body>
      </html>
      `,
      { headers: { "Content-Type": "text/html" } }
    );
  } catch (error) {
    return new Response("Failed to unsubscribe", { status: 500 });
  }
}

Unsubscribe Link Best Practices

Using the Required {{unsubscribe_link}} Placeholder

All marketing emails MUST include the {{unsubscribe_link}} placeholder. This is automatically expanded to the full unsubscribe URL by SendKit:

// In your email template
const html = `
  <html>
    <body>
      <h1>Your Newsletter</h1>
      <p>Great content here...</p>
      
      <!-- REQUIRED: Use {{unsubscribe_link}} placeholder -->
      <footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ccc; font-size: 12px; color: #666;">
        <p>
          You're receiving this email because you subscribed to our newsletter.
          <a href="{{unsubscribe_link}}" style="color: #666;">
            Unsubscribe
          </a>
        </p>
      </footer>
    </body>
  </html>
`;

await sendkit.sendMarketingEmail({
  to: "[email protected]",
  from: { name: "My App", email: "[email protected]" },
  subject: "Newsletter",
  html: html,
});

Note: The {{unsubscribe_link}} placeholder is automatically replaced with the full URL: https://send-kit.com/unsubscribe/{{contact_id}}/{{newsletter_id}} when the email is sent.

Styling Tips for Unobtrusive Unsubscribe Links:

<!-- Minimalist footer style -->
<footer
  style="margin-top: 40px; padding: 20px 0; border-top: 1px solid #e5e7eb; font-size: 11px; color: #9ca3af; text-align: center;"
>
  <a href="{{unsubscribe_link}}" style="color: #9ca3af; text-decoration: none;"
    >Unsubscribe</a
  >
</footer>

<!-- Small text style -->
<div style="font-size: 10px; color: #aaa; margin-top: 30px;">
  <a href="{{unsubscribe_link}}" style="color: #aaa;"
    >Unsubscribe from these emails</a
  >
</div>

<!-- Inline with other footer links -->
<footer
  style="text-align: center; font-size: 12px; color: #666; padding: 20px;"
>
  <a href="https://yoursite.com/privacy">Privacy</a> |
  <a href="https://yoursite.com/terms">Terms</a> |
  <a href="{{unsubscribe_link}}">Unsubscribe</a>
</footer>

Using Your Own Custom Unsubscribe Page

If you're using a custom unsubscribe page, you can use your own URL:

<a href="https://yourdomain.com/unsubscribe?email={{email}}"> Unsubscribe </a>

Security Tip: When passing email addresses in URLs, consider using contact IDs or hashed tokens instead to prevent email harvesting.

Troubleshooting

Common Issues

  1. Invalid API Key

    • Ensure your API key starts with pk_
    • Check that the key is enabled in your SendKit dashboard
    • Verify you're using the correct environment (production vs staging)
  2. Quota Exceeded

    • Check your current quota with getQuota()
    • Upgrade your plan if needed
    • Wait for quota reset
  3. Invalid Email Format

    • Ensure recipient emails are valid
    • Check sender email format
    • Verify email addresses are properly formatted
  4. Network Issues

    • Check your internet connection
    • Verify firewall settings
    • Try again with exponential backoff

Getting Help

License

MIT License - see LICENSE file for details.

Contributing

We welcome contributions! Please see our Contributing Guide for details.