@send-kit/sdk
v1.1.3
Published
Official SendKit SDK for sending emails and managing subscriptions
Maintainers
Readme
SendKit SDK
Official SDK for SendKit - Send beautiful emails with ease.
Installation
npm install @sendkit/sdkQuick 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 operationsFeatures
- 🚀 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 keyconfig.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 operationssendTransactionalEmail(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 trackingcreateNewsletter(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 createdEmail Status Values:
QUEUED- Email is in the queue, waiting to be sentSCHEDULED- Email is scheduled for future deliverySENT- Email has been sent to AWS SESDELIVERED- Email was successfully deliveredOPENED- Recipient opened the emailCLICKED- Recipient clicked a link in the emailBOUNCED- Email bounced (hard or soft bounce)COMPLAINT- Recipient marked email as spamCANCELLED- 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
QUEUEDorSCHEDULEDcan 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: 60Handling 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:
- ✅ You can specify any
from.emailon that domain - ✅ Example: If
@mycompany.comis verified, you can send from:
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
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)
- Ensure your API key starts with
Quota Exceeded
- Check your current quota with
getQuota() - Upgrade your plan if needed
- Wait for quota reset
- Check your current quota with
Invalid Email Format
- Ensure recipient emails are valid
- Check sender email format
- Verify email addresses are properly formatted
Network Issues
- Check your internet connection
- Verify firewall settings
- Try again with exponential backoff
Getting Help
- Documentation: https://docs.send-kit.com
- GitHub Issues: https://github.com/your-org/sendkit/issues
- Support: [email protected]
License
MIT License - see LICENSE file for details.
Contributing
We welcome contributions! Please see our Contributing Guide for details.
