PerspectPerspectDocs
Go to Admin
View as Markdown

Newsletter API

The PerspectAPI v2 SDK provides newsletter subscription management with double opt-in, list segmentation, and campaign access.

Quick Start

import { PerspectApiV2Client } from 'perspectapi-ts-sdk';

const client = new PerspectApiV2Client({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key'
});

// Subscribe a user
const subscription = await client.newsletter.subscribe('my-site', {
  email: 'user@example.com',
  name: 'John Doe',
  double_opt_in: true
});

console.log(subscription.id);     // "nl_42"
console.log(subscription.status); // "pending" (awaiting confirmation)

Subscription Flow

1. Subscribe

const sub = await client.newsletter.subscribe('my-site', {
  email: 'subscriber@example.com',
  name: 'Jane Smith',
  list_ids: ['list_weekly_digest', 'list_product_updates'],
  frequency: 'weekly',
  topics: ['tech', 'business'],
  double_opt_in: true,   // Default: true
  source: 'homepage',
  metadata: {
    campaign: 'summer-2025'
  }
});

Response:

{
  "object": "newsletter_subscription",
  "id": "nl_42",
  "email": "subscriber@example.com",
  "name": "Jane Smith",
  "status": "pending",
  "double_opt_in": true,
  "confirmed_at": null,
  "source": "homepage",
  "frequency": "weekly",
  "topics": ["tech", "business"],
  "created_at": "2025-04-01T12:00:00Z"
}

When double_opt_in is true (default), the user receives a confirmation email. When false, the subscription is confirmed immediately.

2. Confirm (Double Opt-In)

Confirmation is triggered when the user clicks the link in their email. The link contains a token that your frontend passes to the API:

const confirmed = await client.newsletter.confirm('my-site', 'confirmation-token-here');
console.log(confirmed.status); // "confirmed"

3. Unsubscribe

Unsubscribe by token (from email footer link) or by email address:

// By token (from unsubscribe link)
const unsub = await client.newsletter.unsubscribe('my-site', {
  token: 'unsubscribe-token-here',
  reason: 'Too many emails'
});

// By email (admin/API use)
const unsub = await client.newsletter.unsubscribe('my-site', {
  email: 'subscriber@example.com'
});

Admin API

List Subscriptions

const subs = await client.newsletter.listSubscriptions('my-site', {
  limit: 20,
  status: 'confirmed'
});

// {
//   object: "list",
//   data: [{ object: "newsletter_subscription", id: "nl_42", ... }],
//   has_more: true,
//   url: "/v2/sites/my-site/newsletter/subscriptions"
// }

Get Subscription

const sub = await client.newsletter.getSubscription('my-site', 'nl_42');

List Newsletter Lists

const lists = await client.newsletter.listLists('my-site');

for (const list of lists.data) {
  console.log(`${list.name}: ${list.subscriber_count} subscribers`);
}

List Campaigns

const campaigns = await client.newsletter.listCampaigns('my-site', {
  status: 'sent'
});

Get Campaign

// By prefixed ID
const campaign = await client.newsletter.getCampaign('my-site', 'camp_7');

// By slug
const campaign = await client.newsletter.getCampaign('my-site', 'summer-newsletter-2025');

Error Handling

import { PerspectV2Error } from 'perspectapi-ts-sdk';

try {
  await client.newsletter.subscribe('my-site', { email: 'user@test.com' });
} catch (err) {
  if (err instanceof PerspectV2Error) {
    switch (err.code) {
      case 'invalid_list_ids':
        console.error('Unknown list IDs:', err.param);
        break;
      case 'rate_limit_exceeded':
        console.error('Too many requests');
        break;
      default:
        console.error(err.message);
    }
  }
}

Framework Examples

React Subscribe Form

import { PerspectApiV2Client } from 'perspectapi-ts-sdk';

const client = new PerspectApiV2Client({
  baseUrl: import.meta.env.VITE_API_URL,
  apiKey: import.meta.env.VITE_API_KEY
});

function NewsletterForm() {
  const [email, setEmail] = useState('');
  const [status, setStatus] = useState<'idle' | 'pending' | 'confirmed' | 'error'>('idle');

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    try {
      const sub = await client.newsletter.subscribe('my-site', {
        email,
        double_opt_in: true
      });
      setStatus(sub.status === 'pending' ? 'pending' : 'confirmed');
    } catch {
      setStatus('error');
    }
  }

  if (status === 'pending') return <p>Check your email to confirm.</p>;
  if (status === 'confirmed') return <p>Subscribed!</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="your@email.com"
        required
      />
      <button type="submit">Subscribe</button>
    </form>
  );
}