npm package: @send-kit/sdk
Description: Official SendKit SDK for sending emails and managing subscriptions
Author: SendKit Team
Latest version: 1.1.3

# SendKit SDK

Official SDK for SendKit - Send beautiful emails with ease.

## Installation

```bash
npm install @sendkit/sdk
```

## Quick Start

```typescript
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: "user@example.com",
  name: "John",
  surname: "Doe",
});

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

// Send a marketing email
const result = await sendkit.sendMarketingEmail({
  to: "user@example.com",
  from: { name: "My App", email: "noreply@myapp.com" },
  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

```typescript
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.

```typescript
const result = await sendkit.sendMarketingEmail({
  to: "user@example.com",
  from: { name: "My App", email: "noreply@myapp.com" },
  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: "support@myapp.com", // Optional
  cc: "manager@myapp.com", // Optional (can be string or array)
  bcc: ["analytics@myapp.com", "archive@myapp.com"], // 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.

```typescript
const result = await sendkit.sendTransactionalEmail({
  to: "user@example.com",
  from: { name: "My App", email: "noreply@myapp.com" },
  subject: "Order Confirmation",
  html: "<h1>Your order has been confirmed</h1>",
  previewText: "Order confirmation",
  replyTo: "support@myapp.com", // Optional
  cc: "accounting@myapp.com", // Optional (can be string or array)
  bcc: "orders-archive@myapp.com", // 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.

```typescript
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.

```typescript
// 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`

```typescript
{
  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.

```typescript
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**

```typescript
// Send email
const result = await sendkit.sendTransactionalEmail({
  to: "user@example.com",
  from: { name: "MyApp", email: "noreply@myapp.com" },
  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.

```typescript
// Schedule an email for later
const result = await sendkit.sendMarketingEmail({
  to: "subscriber@example.com",
  from: { name: "Newsletter", email: "news@myapp.com" },
  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 subscriber@example.com 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.

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

#### `subscribe(params)`

Subscribe a user to a newsletter.

```typescript
const result = await sendkit.subscribe({
  email: "user@example.com",
  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.

```typescript
const result = await sendkit.unsubscribe({
  email: "user@example.com",
});

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.

```typescript
const result = await sendkit.updateContact({
  email: "user@example.com",
  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.

```typescript
// 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

```typescript
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:

```typescript
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:**

- ✅ You can specify **any** `from.email` on that domain
- ✅ Example: If `@mycompany.com` is verified, you can send from:
  - `noreply@mycompany.com`
  - `support@mycompany.com`
  - `sales@mycompany.com`
  - `john@mycompany.com`

**If your domain is NOT verified:**

- ⚠️ Emails will be sent from `hi@send-kit.com` (SendKit's fallback)
- 💡 The recipient will still see your project name
- 🔐 This protects against domain spoofing

**Example:**

```typescript
// If mycompany.com is verified, this will use support@mycompany.com
await sendkit.sendMarketingEmail({
  to: "customer@example.com",
  from: { name: "Support Team", email: "support@mycompany.com" },
  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: "customer@example.com",
  from: { name: "Sales Team", email: "sales@mycompany.com" },
  subject: "Special Offer",
  html: "<h1>Special offer just for you!</h1>",
});
```

## Error Handling

The SDK throws custom error classes for different scenarios:

```typescript
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

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

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

try {
  const result = await sendkit.sendMarketingEmail({
    to: "customer@example.com",
    from: { name: "My Store", email: "noreply@mystore.com" },
    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: "support@mystore.com",
    bcc: "sales@mystore.com", // 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

```typescript
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: "customer@example.com",
    from: { name: "My Store", email: "orders@mystore.com" },
    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: "support@mystore.com",
    cc: "warehouse@mystore.com", // Notify warehouse
    bcc: "accounting@mystore.com", // 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)

```typescript
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

```typescript
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: "subscriber@example.com",
  from: { name: "Newsletter", email: "news@myapp.com" },
  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 subscriber@example.com 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: "customer@example.com",
  from: { name: "MyApp", email: "noreply@myapp.com" },
  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

```typescript
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

```typescript
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: "user@example.com",
  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: "user@example.com",
});

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:

```html
<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:

```typescript
// 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):**

```typescript
"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="your@email.com"
            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:

```typescript
// 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:

```typescript
// 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: "user@example.com",
  from: { name: "My App", email: "noreply@myapp.com" },
  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:**

```html
<!-- 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:

```html
<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

- **Documentation**: [https://docs.send-kit.com](https://docs.send-kit.com)
- **GitHub Issues**: [https://github.com/your-org/sendkit/issues](https://github.com/your-org/sendkit/issues)
- **Support**: [support@send-kit.com](mailto:support@send-kit.com)

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.