perspectapi-ts-sdk 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Newsletter subscription client for PerspectAPI SDK
3
+ */
4
+
5
+ import { BaseClient } from './base-client';
6
+ import type {
7
+ NewsletterSubscription,
8
+ CreateNewsletterSubscriptionRequest,
9
+ NewsletterList,
10
+ NewsletterPreferences,
11
+ NewsletterStatusResponse,
12
+ NewsletterSubscribeResponse,
13
+ NewsletterConfirmResponse,
14
+ NewsletterUnsubscribeRequest,
15
+ PaginatedResponse,
16
+ ApiResponse,
17
+ } from '../types';
18
+
19
+ export class NewsletterClient extends BaseClient {
20
+ constructor(http: any) {
21
+ super(http, '/api/v1');
22
+ }
23
+
24
+ /**
25
+ * Build a newsletter endpoint scoped to a site (without /sites prefix)
26
+ */
27
+ private newsletterEndpoint(siteName: string, endpoint: string): string {
28
+ return this.siteScopedEndpoint(siteName, endpoint, { includeSitesSegment: false });
29
+ }
30
+
31
+ /**
32
+ * Subscribe to newsletter
33
+ */
34
+ async subscribe(
35
+ siteName: string,
36
+ data: CreateNewsletterSubscriptionRequest
37
+ ): Promise<ApiResponse<NewsletterSubscribeResponse>> {
38
+ return this.create<CreateNewsletterSubscriptionRequest, NewsletterSubscribeResponse>(
39
+ this.newsletterEndpoint(siteName, '/newsletter/subscribe'),
40
+ data
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Confirm newsletter subscription via token
46
+ */
47
+ async confirmSubscription(
48
+ siteName: string,
49
+ token: string
50
+ ): Promise<ApiResponse<NewsletterConfirmResponse>> {
51
+ return this.getSingle(
52
+ this.newsletterEndpoint(siteName, `/newsletter/confirm/${encodeURIComponent(token)}`)
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Unsubscribe from newsletter
58
+ */
59
+ async unsubscribe(
60
+ siteName: string,
61
+ data: NewsletterUnsubscribeRequest
62
+ ): Promise<ApiResponse<{ message: string }>> {
63
+ return this.create<NewsletterUnsubscribeRequest, { message: string }>(
64
+ this.newsletterEndpoint(siteName, '/newsletter/unsubscribe'),
65
+ data
66
+ );
67
+ }
68
+
69
+ /**
70
+ * One-click unsubscribe via token (GET request)
71
+ */
72
+ async unsubscribeByToken(
73
+ siteName: string,
74
+ token: string
75
+ ): Promise<ApiResponse<string>> {
76
+ // This returns HTML, so we return the raw response
77
+ return this.http.get(
78
+ this.buildPath(this.newsletterEndpoint(siteName, `/newsletter/unsubscribe/${encodeURIComponent(token)}`))
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Update subscription preferences
84
+ */
85
+ async updatePreferences(
86
+ siteName: string,
87
+ email: string,
88
+ preferences: NewsletterPreferences
89
+ ): Promise<ApiResponse<{ message: string; preferences: NewsletterPreferences }>> {
90
+ return this.patch(
91
+ this.newsletterEndpoint(siteName, '/newsletter/preferences'),
92
+ { email, ...preferences }
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Get available newsletter lists
98
+ */
99
+ async getLists(siteName: string): Promise<ApiResponse<{
100
+ lists: NewsletterList[];
101
+ total: number;
102
+ }>> {
103
+ return this.getSingle(
104
+ this.newsletterEndpoint(siteName, '/newsletter/lists')
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Check subscription status by email
110
+ */
111
+ async getStatus(
112
+ siteName: string,
113
+ email: string
114
+ ): Promise<ApiResponse<NewsletterStatusResponse>> {
115
+ return this.http.get(
116
+ this.buildPath(this.newsletterEndpoint(siteName, '/newsletter/status')),
117
+ { email }
118
+ );
119
+ }
120
+
121
+ // Admin methods (require authentication)
122
+
123
+ /**
124
+ * Get all newsletter subscriptions (admin only)
125
+ */
126
+ async getSubscriptions(
127
+ siteName: string,
128
+ params?: {
129
+ page?: number;
130
+ limit?: number;
131
+ status?: string;
132
+ list_id?: string;
133
+ search?: string;
134
+ startDate?: string;
135
+ endDate?: string;
136
+ }
137
+ ): Promise<PaginatedResponse<NewsletterSubscription>> {
138
+ return this.getPaginated<NewsletterSubscription>(
139
+ this.newsletterEndpoint(siteName, '/newsletter/subscriptions'),
140
+ params
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Get subscription by ID (admin only)
146
+ */
147
+ async getSubscriptionById(
148
+ siteName: string,
149
+ id: string
150
+ ): Promise<ApiResponse<NewsletterSubscription>> {
151
+ return this.getSingle(
152
+ this.newsletterEndpoint(siteName, `/newsletter/subscriptions/${encodeURIComponent(id)}`)
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Update subscription status (admin only)
158
+ */
159
+ async updateSubscriptionStatus(
160
+ siteName: string,
161
+ id: string,
162
+ status: 'confirmed' | 'unsubscribed' | 'bounced' | 'complained',
163
+ notes?: string
164
+ ): Promise<ApiResponse<{ message: string }>> {
165
+ return this.patch(
166
+ this.newsletterEndpoint(siteName, `/newsletter/subscriptions/${encodeURIComponent(id)}`),
167
+ { status, notes }
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Delete subscription (admin only)
173
+ */
174
+ async deleteSubscription(
175
+ siteName: string,
176
+ id: string
177
+ ): Promise<ApiResponse<{ message: string }>> {
178
+ return this.delete<{ message: string }>(
179
+ this.newsletterEndpoint(siteName, `/newsletter/subscriptions/${encodeURIComponent(id)}`)
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Bulk update subscriptions (admin only)
185
+ */
186
+ async bulkUpdateSubscriptions(
187
+ siteName: string,
188
+ data: {
189
+ ids: string[];
190
+ action: 'confirm' | 'unsubscribe' | 'delete' | 'add_to_list' | 'remove_from_list';
191
+ list_id?: string;
192
+ }
193
+ ): Promise<ApiResponse<{
194
+ success: boolean;
195
+ updatedCount: number;
196
+ failedCount: number;
197
+ }>> {
198
+ return this.create(
199
+ this.newsletterEndpoint(siteName, '/newsletter/subscriptions/bulk-update'),
200
+ data
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Create newsletter list (admin only)
206
+ */
207
+ async createList(
208
+ siteName: string,
209
+ data: {
210
+ list_name: string;
211
+ slug: string;
212
+ description?: string;
213
+ is_public?: boolean;
214
+ is_default?: boolean;
215
+ double_opt_in?: boolean;
216
+ welcome_email_enabled?: boolean;
217
+ }
218
+ ): Promise<ApiResponse<NewsletterList>> {
219
+ return this.create<any, NewsletterList>(
220
+ this.newsletterEndpoint(siteName, '/newsletter/lists'),
221
+ data
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Update newsletter list (admin only)
227
+ */
228
+ async updateList(
229
+ siteName: string,
230
+ listId: string,
231
+ data: Partial<{
232
+ list_name: string;
233
+ description: string;
234
+ is_public: boolean;
235
+ is_default: boolean;
236
+ double_opt_in: boolean;
237
+ welcome_email_enabled: boolean;
238
+ }>
239
+ ): Promise<ApiResponse<{ message: string }>> {
240
+ return this.update(
241
+ this.newsletterEndpoint(siteName, `/newsletter/lists/${encodeURIComponent(listId)}`),
242
+ data
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Delete newsletter list (admin only)
248
+ */
249
+ async deleteList(
250
+ siteName: string,
251
+ listId: string
252
+ ): Promise<ApiResponse<{ message: string }>> {
253
+ return this.delete<{ message: string }>(
254
+ this.newsletterEndpoint(siteName, `/newsletter/lists/${encodeURIComponent(listId)}`)
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Get newsletter statistics (admin only)
260
+ */
261
+ async getStatistics(
262
+ siteName: string,
263
+ params?: {
264
+ startDate?: string;
265
+ endDate?: string;
266
+ list_id?: string;
267
+ }
268
+ ): Promise<ApiResponse<{
269
+ totalSubscribers: number;
270
+ confirmedSubscribers: number;
271
+ pendingSubscribers: number;
272
+ unsubscribedCount: number;
273
+ bouncedCount: number;
274
+ subscribersByDay: Array<{
275
+ date: string;
276
+ count: number;
277
+ }>;
278
+ subscribersByList: Array<{
279
+ list_id: string;
280
+ list_name: string;
281
+ count: number;
282
+ }>;
283
+ engagementMetrics: {
284
+ averageOpenRate: number;
285
+ averageClickRate: number;
286
+ };
287
+ }>> {
288
+ return this.http.get(
289
+ this.buildPath(this.newsletterEndpoint(siteName, '/newsletter/statistics')),
290
+ params
291
+ );
292
+ }
293
+
294
+ /**
295
+ * Export newsletter subscriptions (admin only)
296
+ */
297
+ async exportSubscriptions(
298
+ siteName: string,
299
+ params?: {
300
+ format?: 'csv' | 'json' | 'xlsx';
301
+ status?: string;
302
+ list_id?: string;
303
+ startDate?: string;
304
+ endDate?: string;
305
+ }
306
+ ): Promise<ApiResponse<{
307
+ downloadUrl: string;
308
+ expiresAt: string;
309
+ }>> {
310
+ return this.create(
311
+ this.newsletterEndpoint(siteName, '/newsletter/export'),
312
+ params || {}
313
+ );
314
+ }
315
+
316
+ /**
317
+ * Import newsletter subscriptions (admin only)
318
+ */
319
+ async importSubscriptions(
320
+ siteName: string,
321
+ data: {
322
+ subscriptions: Array<{
323
+ email: string;
324
+ name?: string;
325
+ status?: string;
326
+ lists?: string[];
327
+ }>;
328
+ skip_confirmation?: boolean;
329
+ update_existing?: boolean;
330
+ }
331
+ ): Promise<ApiResponse<{
332
+ imported: number;
333
+ updated: number;
334
+ failed: number;
335
+ errors?: Array<{ email: string; error: string }>;
336
+ }>> {
337
+ return this.create(
338
+ this.newsletterEndpoint(siteName, '/newsletter/import'),
339
+ data
340
+ );
341
+ }
342
+
343
+ /**
344
+ * Send test newsletter (admin only)
345
+ */
346
+ async sendTestNewsletter(
347
+ siteName: string,
348
+ data: {
349
+ to: string;
350
+ subject: string;
351
+ html_content: string;
352
+ text_content?: string;
353
+ }
354
+ ): Promise<ApiResponse<{ message: string; sent: boolean }>> {
355
+ return this.create(
356
+ this.newsletterEndpoint(siteName, '/newsletter/test'),
357
+ data
358
+ );
359
+ }
360
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export { CategoriesClient } from './client/categories-client';
18
18
  export { WebhooksClient } from './client/webhooks-client';
19
19
  export { CheckoutClient } from './client/checkout-client';
20
20
  export { ContactClient } from './client/contact-client';
21
+ export { NewsletterClient } from './client/newsletter-client';
21
22
 
22
23
  // Base classes
23
24
  export { BaseClient } from './client/base-client';
@@ -71,4 +72,12 @@ export type {
71
72
  ContactSubmission,
72
73
  CheckoutSession,
73
74
  PaymentGateway,
75
+ NewsletterSubscription,
76
+ CreateNewsletterSubscriptionRequest,
77
+ NewsletterList,
78
+ NewsletterPreferences,
79
+ NewsletterStatusResponse,
80
+ NewsletterSubscribeResponse,
81
+ NewsletterConfirmResponse,
82
+ NewsletterUnsubscribeRequest,
74
83
  } from './types';
@@ -14,6 +14,7 @@ import { CategoriesClient } from './client/categories-client';
14
14
  import { WebhooksClient } from './client/webhooks-client';
15
15
  import { CheckoutClient } from './client/checkout-client';
16
16
  import { ContactClient } from './client/contact-client';
17
+ import { NewsletterClient } from './client/newsletter-client';
17
18
 
18
19
  import type { PerspectApiConfig, ApiResponse } from './types';
19
20
 
@@ -31,6 +32,7 @@ export class PerspectApiClient {
31
32
  public readonly webhooks: WebhooksClient;
32
33
  public readonly checkout: CheckoutClient;
33
34
  public readonly contact: ContactClient;
35
+ public readonly newsletter: NewsletterClient;
34
36
 
35
37
  constructor(config: PerspectApiConfig) {
36
38
  // Validate required configuration
@@ -52,6 +54,7 @@ export class PerspectApiClient {
52
54
  this.webhooks = new WebhooksClient(this.http);
53
55
  this.checkout = new CheckoutClient(this.http);
54
56
  this.contact = new ContactClient(this.http);
57
+ this.newsletter = new NewsletterClient(this.http);
55
58
  }
56
59
 
57
60
  /**
@@ -119,6 +119,83 @@ export interface CreateSiteRequest {
119
119
  organizationId: number;
120
120
  }
121
121
 
122
+ // Newsletter Subscriptions
123
+ export interface NewsletterSubscription {
124
+ id: string;
125
+ email: string;
126
+ name?: string;
127
+ status: 'pending' | 'confirmed' | 'unsubscribed' | 'bounced' | 'complained';
128
+ frequency: 'instant' | 'daily' | 'weekly' | 'monthly';
129
+ topics?: string[];
130
+ language: string;
131
+ confirmedAt?: string;
132
+ unsubscribedAt?: string;
133
+ createdAt: string;
134
+ updatedAt: string;
135
+ }
136
+
137
+ export interface CreateNewsletterSubscriptionRequest {
138
+ email: string;
139
+ name?: string;
140
+ list_ids?: string[];
141
+ frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
142
+ topics?: string[];
143
+ language?: string;
144
+ source?: string;
145
+ source_url?: string;
146
+ double_opt_in?: boolean;
147
+ turnstile_token?: string;
148
+ metadata?: Record<string, any>;
149
+ }
150
+
151
+ export interface NewsletterList {
152
+ id: string;
153
+ list_name: string;
154
+ slug: string;
155
+ description?: string;
156
+ is_default: boolean;
157
+ subscriber_count?: number;
158
+ }
159
+
160
+ export interface NewsletterPreferences {
161
+ frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
162
+ topics?: string[];
163
+ language?: string;
164
+ email_format?: 'html' | 'text' | 'both';
165
+ timezone?: string;
166
+ track_opens?: boolean;
167
+ track_clicks?: boolean;
168
+ }
169
+
170
+ export interface NewsletterStatusResponse {
171
+ subscribed: boolean;
172
+ status: string;
173
+ frequency?: string;
174
+ created_at?: string;
175
+ confirmed_at?: string;
176
+ }
177
+
178
+ export interface NewsletterSubscribeResponse {
179
+ message: string;
180
+ status: string;
181
+ subscription_id?: string;
182
+ }
183
+
184
+ export interface NewsletterConfirmResponse {
185
+ message: string;
186
+ subscription?: {
187
+ email: string;
188
+ name?: string;
189
+ frequency: string;
190
+ };
191
+ }
192
+
193
+ export interface NewsletterUnsubscribeRequest {
194
+ token?: string;
195
+ email?: string;
196
+ reason?: string;
197
+ }
198
+
122
199
  // Content Management
123
200
  export type ContentStatus = 'draft' | 'publish' | 'private' | 'trash';
124
201
  export type ContentType = 'post' | 'page';