migma 1.3.2 → 1.3.4

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.
package/README.md CHANGED
@@ -36,7 +36,7 @@ const { data: email } = await migma.emails.generateAndWait({
36
36
  if (email?.status === 'completed') {
37
37
  console.log('Subject:', email.result.subject);
38
38
  console.log('HTML:', email.result.html); // primary email HTML
39
- console.log('Artifact:', email.result.emails[0].artifactId);
39
+ console.log('Email ID:', email.result.emails[0].emailId);
40
40
  }
41
41
  ```
42
42
 
@@ -66,20 +66,20 @@ const { data: email } = await migma.emails.generateAndWait({
66
66
 
67
67
  if (!email?.result) throw new Error('Email generation failed');
68
68
 
69
- // 3. Send it — artifactId works for single emails and series slots
69
+ // 3. Send it — emailId works for single emails and series slots
70
70
  const selectedEmail = email.result.emails[0];
71
- if (!selectedEmail.artifactId) throw new Error('No artifactId returned');
71
+ if (!selectedEmail.emailId) throw new Error('No emailId returned');
72
72
  await migma.sending.send({
73
73
  recipientType: 'email',
74
74
  recipientEmail: 'sarah@example.com',
75
75
  from: 'hello@yourcompany.migma.email',
76
76
  fromName: 'Your Company',
77
77
  subject: selectedEmail.subject,
78
- artifactId: selectedEmail.artifactId,
78
+ emailId: selectedEmail.emailId,
79
79
  });
80
80
 
81
81
  // conversationId also works for single-email conversations.
82
- // For multi-slot emails or series, use result.emails[].artifactId.
82
+ // For multi-slot emails or series, use result.emails[].emailId.
83
83
  ```
84
84
 
85
85
  [Full OpenClaw tutorial](https://docs.migma.ai/tutorials/send-emails-from-openclaw)
@@ -126,6 +126,7 @@ Every method returns `{ data, error }` instead of throwing. TypeScript narrows t
126
126
  ```typescript
127
127
  const { data, error } = await migma.contacts.create({
128
128
  email: 'john@example.com',
129
+ status: 'subscribed', // only for contacts with marketing consent
129
130
  projectId: 'proj_abc123',
130
131
  });
131
132
 
@@ -206,7 +207,7 @@ const { data: contact } = await migma.contacts.create({
206
207
  // List with filters
207
208
  const { data: contacts } = await migma.contacts.list({
208
209
  projectId: 'proj_abc123',
209
- status: 'active',
210
+ status: 'subscribed',
210
211
  tags: 'newsletter',
211
212
  limit: 50,
212
213
  });
@@ -219,6 +220,19 @@ const { data: result } = await migma.contacts.bulkImport({
219
220
  ],
220
221
  projectId: 'proj_abc123',
221
222
  });
223
+
224
+ // Batch delete by email (up to 1000; unmatched emails come back in notFound)
225
+ const { data: deletion } = await migma.contacts.bulkDeleteByEmail({
226
+ emails: ['a@example.com', 'b@example.com'],
227
+ projectId: 'proj_abc123',
228
+ });
229
+
230
+ // Safe retries: pass an Idempotency-Key so a retried write never duplicates.
231
+ // Same key + same body within 24h replays the original response.
232
+ await migma.contacts.create(
233
+ { email: 'jane@example.com', projectId: 'proj_abc123' },
234
+ { idempotencyKey: 'signup-jane@example.com-1779292800' },
235
+ );
222
236
  ```
223
237
 
224
238
  [Contacts API Reference](https://docs.migma.ai/api-reference/contacts/get-contact)
@@ -228,14 +242,14 @@ const { data: result } = await migma.contacts.bulkImport({
228
242
  Send to a single recipient, segment, tag, or full audience.
229
243
 
230
244
  ```typescript
231
- // Send a generated email by artifactId
245
+ // Send a generated email by emailId
232
246
  await migma.sending.send({
233
247
  recipientType: 'email',
234
248
  recipientEmail: 'user@example.com',
235
249
  from: 'hello@yourdomain.com',
236
250
  fromName: 'Your Company',
237
251
  subject: 'Welcome!',
238
- artifactId: 'artifact_abc123',
252
+ emailId: 'email_abc123',
239
253
  });
240
254
 
241
255
  // Single-email conversations can also be sent by conversationId.
@@ -255,7 +269,7 @@ await migma.sending.send({
255
269
  from: 'hello@yourdomain.com',
256
270
  fromName: 'Your Company',
257
271
  subject: 'Big Announcement',
258
- artifactId: 'artifact_abc123',
272
+ emailId: 'email_abc123',
259
273
  });
260
274
 
261
275
  // Single sends are automatically transactional — no flag needed
@@ -266,7 +280,7 @@ await migma.sending.send({
266
280
  from: 'noreply@yourdomain.com',
267
281
  fromName: 'Your Company',
268
282
  subject: 'Your subscription renews tomorrow',
269
- artifactId: 'artifact_abc123',
283
+ emailId: 'email_abc123',
270
284
  transactional: true,
271
285
  });
272
286
  ```
@@ -275,7 +289,7 @@ await migma.sending.send({
275
289
 
276
290
  ### Generated Series and Editing
277
291
 
278
- Generate a series with `count`, then use each email's `artifactId` to fetch, compile, update, or send one slot.
292
+ Generate a series with `count`, then use each email's `emailId` to fetch, prompt-edit, or send one email.
279
293
 
280
294
  ```typescript
281
295
  const { data: series } = await migma.emails.generateAndWait({
@@ -285,21 +299,135 @@ const { data: series } = await migma.emails.generateAndWait({
285
299
  });
286
300
 
287
301
  for (const generated of series?.result?.emails ?? []) {
288
- console.log(generated.slot, generated.artifactId, generated.html);
302
+ console.log(generated.slot, generated.emailId, generated.html);
303
+ console.log(generated.screenshotUrl);
289
304
  }
290
305
 
291
- const { data: email } = await migma.emails.get('artifact_abc123');
292
- if (!email?.source) throw new Error('Editable source unavailable');
306
+ const { data: email } = await migma.emails.get('email_abc123');
307
+ console.log(email?.html);
308
+
309
+ await migma.emails.edit('email_abc123', {
310
+ prompt: 'Change the CTA from "Start now" to "Start your trial"',
311
+ });
312
+ ```
313
+
314
+ ### Campaigns
315
+
316
+ Create a campaign from a generated email, send or schedule it, then read its
317
+ engagement stats and per-recipient logs.
318
+
319
+ ```typescript
320
+ // Create from a generated email
321
+ const { data: campaign } = await migma.campaigns.create({
322
+ projectId: 'proj_abc123',
323
+ name: 'Spring Launch',
324
+ conversationId: 'conv_abc123',
325
+ emailId: 'email_abc123',
326
+ subject: 'Spring is here',
327
+ from: 'hello@yourdomain.com',
328
+ fromName: 'Your Brand',
329
+ recipientType: 'audience',
330
+ recipientId: 'aud_abc123',
331
+ });
332
+
333
+ // Send now (idempotency key recommended — see below)
334
+ await migma.campaigns.send(campaign.id, {
335
+ idempotencyKey: `campaign-send-${campaign.id}`,
336
+ });
337
+
338
+ // Or schedule for later
339
+ await migma.campaigns.schedule(
340
+ campaign.id,
341
+ { scheduledAt: '2026-07-01T15:00:00Z', scheduledTimezone: 'America/New_York' },
342
+ { idempotencyKey: `campaign-send-${campaign.id}` },
343
+ );
293
344
 
294
- await migma.emails.compile('artifact_abc123', {
295
- source: email.source.replace('Start now', 'Start your trial'),
345
+ // Aggregated engagement metrics (cached, may be slightly stale)
346
+ const { data: stats } = await migma.campaigns.stats(campaign.id);
347
+ console.log(stats?.openRate, stats?.clickRate, stats?.lastUpdated);
348
+
349
+ // Per-recipient logs, cursor-paginated and optionally filtered by status
350
+ const { data: logs } = await migma.campaigns.logs(campaign.id, {
351
+ status: 'opened',
352
+ limit: 50,
296
353
  });
354
+ for (const row of logs?.emails ?? []) {
355
+ console.log(row.to_email, row.status, row.opened_at);
356
+ }
357
+ if (logs?.hasMore) {
358
+ await migma.campaigns.logs(campaign.id, { cursor: logs.nextCursor ?? undefined });
359
+ }
360
+ ```
361
+
362
+ Campaign stats are sourced from the email tracking worker and cached on the
363
+ campaign, so they can lag live activity. `botOpens`, `botClicks`, and `mppOpens`
364
+ reflect Apple Mail Privacy Protection and bot detection when that data is
365
+ available. Log rows come straight from the tracking store, so their field names
366
+ are `snake_case` (`to_email`, `opened_at`, `bounce_type`) unlike the rest of the
367
+ API.
297
368
 
298
- await migma.emails.update('artifact_abc123', {
299
- source: email.source.replace('Start now', 'Start your trial'),
369
+ [Campaigns Guide](https://docs.migma.ai/campaigns/overview)
370
+
371
+ ### Idempotency
372
+
373
+ Write methods accept an optional trailing `CallOptions` argument carrying an
374
+ `idempotencyKey`. The key is sent as the `Idempotency-Key` header (max 100
375
+ characters) and is scoped to your API key for 24 hours:
376
+
377
+ - **Same key + same body** within 24h replays the original response (the server
378
+ marks the replay with an `Idempotent-Replayed: true` header).
379
+ - **Same key + different body** returns `409 IDEMPOTENCY_CONFLICT`.
380
+ - `429` and `5xx` responses are never cached, so they stay safe to retry — and
381
+ the SDK reuses your key across its own automatic retries, so a retried call
382
+ never duplicates.
383
+
384
+ ```typescript
385
+ // Sending — key on the recipient + send time
386
+ await migma.sending.send(
387
+ {
388
+ recipientType: 'email',
389
+ recipientEmail: 'user@example.com',
390
+ from: 'hello@yourdomain.com',
391
+ fromName: 'Your Brand',
392
+ subject: 'Welcome!',
393
+ emailId: 'email_abc123',
394
+ },
395
+ { idempotencyKey: 'send-email_abc123-user@example.com-now' },
396
+ );
397
+
398
+ // Campaign send — one key per campaign send
399
+ await migma.campaigns.send('camp_abc123', {
400
+ idempotencyKey: 'campaign-send-camp_abc123',
300
401
  });
402
+
403
+ // Email generation — key on project + the turn/prompt
404
+ await migma.emails.generate(
405
+ { projectId: 'proj_abc123', prompt: 'Welcome email' },
406
+ { idempotencyKey: 'email-gen-proj_abc123-welcome' },
407
+ );
408
+
409
+ // Contact add — key on the email
410
+ await migma.contacts.create(
411
+ { email: 'jane@example.com', projectId: 'proj_abc123' },
412
+ { idempotencyKey: 'contact-jane@example.com' },
413
+ );
301
414
  ```
302
415
 
416
+ **Choose a deterministic key** so the same logical operation produces the same
417
+ key on every attempt:
418
+
419
+ - send: `send-<emailId>-<recipientId|to>-<scheduledAt|"now">`
420
+ - campaign send: `campaign-send-<campaignId>`
421
+ - email generate: `email-gen-<projectId>-<turnOrPromptHash>`
422
+ - contact add: `contact-<email>`
423
+
424
+ **Anti-patterns to avoid:**
425
+
426
+ - Don't generate the key with `uuid()` or `Date.now()` per call — a fresh key
427
+ every attempt defeats deduplication across retries and process restarts.
428
+ - Don't hash the request body to build the key — identical retries you *want*
429
+ deduplicated would each get a different key, and you lose the conflict signal.
430
+
303
431
  ### Email Validation
304
432
 
305
433
  Run preflight checks — compatibility, links, spelling, and deliverability.
@@ -377,6 +505,14 @@ const { data: avail } = await migma.domains.checkAvailability('mycompany');
377
505
  if (avail.available) {
378
506
  await migma.domains.createManaged({ prefix: 'mycompany' });
379
507
  }
508
+
509
+ // Provision a transactional stream (notify.example.com) to isolate
510
+ // transactional reputation from marketing, then publish the returned DNS records
511
+ await migma.domains.provisionStream({ rootDomain: 'example.com', stream: 'transactional' });
512
+
513
+ // Or provision both streams (send. marketing + notify. transactional) in one call
514
+ const { data } = await migma.domains.setup({ rootDomain: 'example.com' });
515
+ // data.marketing, data.transactional
380
516
  ```
381
517
 
382
518
  [Domains API Reference](https://docs.migma.ai/api-reference/domains/list-domains)
@@ -394,7 +530,19 @@ const { data: tag } = await migma.tags.create({
394
530
  // Segments
395
531
  const { data: segment } = await migma.segments.create({
396
532
  name: 'Active US Customers',
397
- filters: { status: 'subscribed', customFields: { country: ['US'] } },
533
+ filters: {
534
+ status: 'subscribed',
535
+ fields: [{ key: 'country', mode: 'is', values: ['US'] }],
536
+ activity: [{ action: 'opened', channel: 'email', mode: 'within', unit: 'days', amount: 30 }],
537
+ },
538
+ projectId: 'proj_abc123',
539
+ });
540
+
541
+ const { data: campaignSegment } = await migma.segments.create({
542
+ name: 'Campaign openers',
543
+ filters: {
544
+ activity: [{ action: 'opened', channel: 'email', mode: 'within', unit: 'days', amount: 14, campaignId: 'cmp_123' }],
545
+ },
398
546
  projectId: 'proj_abc123',
399
547
  });
400
548
 
@@ -457,12 +605,13 @@ await migma.images.updateLogos('proj_abc123', {
457
605
  | `migma.segments` | `create` `list` `get` `update` `remove` | [Segments](https://docs.migma.ai/api-reference/audiences/list-audiences) |
458
606
  | `migma.topics` | `create` `list` `get` `update` `remove` `subscribe` `unsubscribe` | [Topics](https://docs.migma.ai/api-reference/topics/list-topics) |
459
607
  | `migma.sending` | `send` `getBatchStatus` | [Sending](https://docs.migma.ai/api-reference/sending/send-email) |
460
- | `migma.projects` | `list` `get` `import` `getImportStatus` `retryImport` `importAndWait` | [Projects](https://docs.migma.ai/api-reference/projects/list-projects) |
461
- | `migma.emails` | `generate` `getGenerationStatus` `generateAndWait` `sendTest` | [Email Generation](https://docs.migma.ai/api-reference/email/generate-email-async) |
608
+ | `migma.projects` | `list` `get` `import` `getImportStatus` `retryImport` `fieldCatalog` `importAndWait` | [Projects](https://docs.migma.ai/api-reference/projects/list-projects) |
609
+ | `migma.emails` | `generate` `getGenerationStatus` `generateAndWait` `sendTest` `get` `edit` | [Email Generation](https://docs.migma.ai/api-reference/email/generate-email-async) |
610
+ | `migma.campaigns` | `list` `create` `get` `send` `schedule` `cancel` `stats` `logs` `archive` `unarchive` | [Campaigns](https://docs.migma.ai/campaigns/overview) |
462
611
  | `migma.validation` | `all` `compatibility` `links` `spelling` `deliverability` | [Validation](https://docs.migma.ai/api-reference/email-validation/run-all-validation-checks) |
463
612
  | `migma.previews` | `create` `get` `getStatus` `getDevice` `getSupportedDevices` `createAndWait` | [Previews](https://docs.migma.ai/api-reference/email-previews/create-email-preview) |
464
613
  | `migma.export` | `getFormats` `getStatus` `html` `mjml` `pdf` `klaviyo` `mailchimp` `hubspot` | [Export](https://docs.migma.ai/api-reference/export/list-export-formats) |
465
- | `migma.domains` | `create` `list` `get` `verify` `update` `remove` `checkAvailability` `listManaged` `createManaged` `removeManaged` | [Domains](https://docs.migma.ai/api-reference/domains/list-domains) |
614
+ | `migma.domains` | `create` `list` `get` `verify` `update` `remove` `checkAvailability` `listManaged` `createManaged` `removeManaged` `provisionStream` `setup` | [Domains](https://docs.migma.ai/api-reference/domains/list-domains) |
466
615
  | `migma.webhooks` | `create` `list` `get` `update` `remove` `test` `getDeliveries` `getEvents` `getStats` | [Webhooks](https://docs.migma.ai/webhooks) |
467
616
  | `migma.knowledgeBase` | `list` `add` `update` `remove` | [API Ref](https://docs.migma.ai/api-reference/introduction) |
468
617
  | `migma.images` | `add` `update` `remove` `updateLogos` | [API Ref](https://docs.migma.ai/api-reference/introduction) |