@sendly/node 3.27.2 → 3.30.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.
package/dist/index.d.mts CHANGED
@@ -877,7 +877,12 @@ declare const ALL_SUPPORTED_COUNTRIES: string[];
877
877
  /**
878
878
  * Webhook event types
879
879
  */
880
- type WebhookEventType = "message.sent" | "message.delivered" | "message.failed" | "message.bounced" | "message.retrying" | "message.queued" | "message.received" | "message.opt_out" | "message.opt_in" | "verification.created" | "verification.delivered" | "verification.verified" | "verification.expired" | "verification.failed" | "verification.resent" | "verification.delivery_failed";
880
+ type WebhookEventType = "message.sent" | "message.delivered" | "message.failed" | "message.bounced" | "message.retrying" | "message.queued" | "message.received" | "message.opt_out" | "message.opt_in" | "verification.created" | "verification.delivered" | "verification.verified" | "verification.expired" | "verification.failed" | "verification.resent" | "verification.delivery_failed" | "contact.auto_flagged" | "contact.marked_valid" | "contacts.lookup_completed" | "contacts.bulk_marked_valid";
881
+ /**
882
+ * Source of a list-health event. Frozen enum — new values will be
883
+ * added in minor SDK versions, never removed.
884
+ */
885
+ type ListHealthEventSource = "send_failure" | "carrier_lookup" | "user_action" | "bulk_mark_valid";
881
886
  /**
882
887
  * Webhook mode - filters which events are delivered
883
888
  * - "all": Receives all events (sandbox + production)
@@ -973,6 +978,72 @@ interface UpdateWebhookOptions {
973
978
  /** Custom metadata */
974
979
  metadata?: Record<string, unknown>;
975
980
  }
981
+ /**
982
+ * Options for replaying webhook deliveries
983
+ */
984
+ interface WebhookRedeliverOptions {
985
+ /** Earliest delivery created_at to consider, ISO-8601 (default: now − 24h) */
986
+ since?: string;
987
+ /** Latest delivery created_at to consider, ISO-8601 (default: now) */
988
+ until?: string;
989
+ /** Filter by event type (default: all) */
990
+ eventTypes?: WebhookEventType[];
991
+ /** Replay deliveries in any of these statuses (default: ['failed', 'cancelled']) */
992
+ statuses?: DeliveryStatus[];
993
+ /** Maximum number of deliveries to requeue (default: 1000, max 10000) */
994
+ limit?: number;
995
+ }
996
+ /**
997
+ * Result of replaying webhook deliveries
998
+ */
999
+ interface WebhookRedeliverResult {
1000
+ message: string;
1001
+ /** Number of deliveries that were re-queued */
1002
+ requeued: number;
1003
+ /** Number of deliveries that failed to re-queue */
1004
+ skipped: number;
1005
+ /** True if the matching set was larger than `limit` */
1006
+ truncated: boolean;
1007
+ /** Total number of matching deliveries before the limit was applied */
1008
+ windowSize: number;
1009
+ /** IDs of the new delivery records created by the replay */
1010
+ deliveryIds: string[];
1011
+ since: string;
1012
+ until: string;
1013
+ limit: number;
1014
+ }
1015
+ /**
1016
+ * Options for backfilling missed webhook deliveries from the message log
1017
+ */
1018
+ interface WebhookBackfillOptions {
1019
+ /** Earliest message created_at to consider, ISO-8601 (default: now − 24h) */
1020
+ since?: string;
1021
+ /** Latest message created_at to consider, ISO-8601 (default: now) */
1022
+ until?: string;
1023
+ /** Filter by event type (default: subscribed message events) */
1024
+ eventTypes?: WebhookEventType[];
1025
+ /** Maximum number of events to synthesize (default: 1000, max 10000) */
1026
+ limit?: number;
1027
+ }
1028
+ /**
1029
+ * Result of backfilling missed webhook deliveries
1030
+ */
1031
+ interface WebhookBackfillResult {
1032
+ message: string;
1033
+ /** Number of deliveries synthesized and dispatched */
1034
+ synthesized: number;
1035
+ /** Synthesized count grouped by event type */
1036
+ byType: Record<string, number>;
1037
+ /** True if there were more eligible events than `limit` */
1038
+ truncated: boolean;
1039
+ /** Total number of messages scanned for the window */
1040
+ candidatesScanned: number;
1041
+ /** IDs of the new delivery records */
1042
+ deliveryIds: string[];
1043
+ since: string;
1044
+ until: string;
1045
+ limit: number;
1046
+ }
976
1047
  /**
977
1048
  * A webhook delivery attempt
978
1049
  */
@@ -1527,6 +1598,39 @@ interface Contact {
1527
1598
  * Whether the contact has opted out
1528
1599
  */
1529
1600
  optedOut?: boolean;
1601
+ /**
1602
+ * Carrier-reported line type for this number. One of: `mobile`, `voip`,
1603
+ * `toll free`, `fixed line`, `fixed line or mobile`, `pager`, `voicemail`,
1604
+ * `shared cost`, `premium rate`, `uan`, `personal number`, `unknown`.
1605
+ * Populated after a carrier lookup (either automatic or via checkNumbers).
1606
+ */
1607
+ lineType?: string | null;
1608
+ /**
1609
+ * Carrier name reported by the lookup (e.g., "AT&T", "Verizon").
1610
+ */
1611
+ carrierName?: string | null;
1612
+ /**
1613
+ * When the carrier lookup last ran for this contact.
1614
+ */
1615
+ lineTypeCheckedAt?: string | null;
1616
+ /**
1617
+ * Reason this contact is excluded from future campaigns. One of:
1618
+ * `landline`, `invalid_number`, `non_sms_capable`. Set automatically
1619
+ * after terminal send failures or by a carrier lookup. Clear it with
1620
+ * `contacts.markValid(id)`.
1621
+ */
1622
+ invalidReason?: string | null;
1623
+ /**
1624
+ * When the invalid flag was set.
1625
+ */
1626
+ invalidatedAt?: string | null;
1627
+ /**
1628
+ * When a user manually cleared an auto-flag on this contact. Carrier
1629
+ * re-checks that would re-flag the contact as invalid respect this
1630
+ * timestamp and leave the contact clean, so your manual decisions
1631
+ * survive future lookups.
1632
+ */
1633
+ userMarkedValidAt?: string | null;
1530
1634
  /**
1531
1635
  * When the contact was created
1532
1636
  */
@@ -1543,6 +1647,38 @@ interface Contact {
1543
1647
  name: string;
1544
1648
  }>;
1545
1649
  }
1650
+ /**
1651
+ * Response from triggering a bulk carrier lookup via `contacts.checkNumbers()`.
1652
+ */
1653
+ interface CheckNumbersResponse {
1654
+ success: boolean;
1655
+ /**
1656
+ * True if a carrier lookup for this scope was already running when you
1657
+ * called this. The dashboard renders an "already in progress" toast when
1658
+ * it sees this flag; server-side integrators can treat it as a soft
1659
+ * no-op and wait for `contacts.lookup_completed` webhook.
1660
+ */
1661
+ alreadyRunning?: boolean;
1662
+ message?: string;
1663
+ }
1664
+ /**
1665
+ * Scope for `contacts.bulkMarkValid()`. Pass either an explicit id array
1666
+ * (up to 10,000 per call) OR a `listId` — not both. Foreign ids silently
1667
+ * no-op via the per-org filter.
1668
+ */
1669
+ interface BulkMarkValidOptions {
1670
+ /** Explicit contact ids to clear (max 10,000). */
1671
+ ids?: string[];
1672
+ /** Clear every flagged member of this list. */
1673
+ listId?: string;
1674
+ }
1675
+ /**
1676
+ * Response from `contacts.bulkMarkValid()`.
1677
+ */
1678
+ interface BulkMarkValidResponse {
1679
+ /** Number of contacts whose invalid flag was actually cleared. */
1680
+ cleared: number;
1681
+ }
1546
1682
  /**
1547
1683
  * Request to create a contact
1548
1684
  */
@@ -2581,6 +2717,62 @@ declare class WebhooksResource {
2581
2717
  message: string;
2582
2718
  webhook: Webhook;
2583
2719
  }>;
2720
+ /**
2721
+ * Replay failed or cancelled webhook deliveries from the audit log.
2722
+ *
2723
+ * Use after a customer endpoint has recovered from an outage to re-fire
2724
+ * deliveries that we recorded but couldn't deliver. Each replay creates
2725
+ * a new delivery row preserving the original `event_id` so customers can
2726
+ * dedupe.
2727
+ *
2728
+ * Rejects with HTTP 409 if the circuit is currently open — call
2729
+ * {@link WebhooksResource.resetCircuit} first.
2730
+ *
2731
+ * @param id - Webhook ID
2732
+ * @param options - Window and filter options
2733
+ * @returns Counts of requeued deliveries plus the new delivery IDs
2734
+ *
2735
+ * @example
2736
+ * ```typescript
2737
+ * await sendly.webhooks.resetCircuit('whk_xxx');
2738
+ * const result = await sendly.webhooks.redeliver('whk_xxx', {
2739
+ * since: '2026-05-01T00:00:00Z',
2740
+ * eventTypes: ['message.delivered', 'message.failed'],
2741
+ * limit: 5000,
2742
+ * });
2743
+ * console.log(`Requeued ${result.requeued} deliveries`);
2744
+ * ```
2745
+ */
2746
+ redeliver(id: string, options?: WebhookRedeliverOptions): Promise<WebhookRedeliverResult>;
2747
+ /**
2748
+ * Backfill missed webhook events from the underlying message log.
2749
+ *
2750
+ * Use this when a circuit-breaker outage left events with no audit row
2751
+ * (the case `redeliver` cannot recover). The endpoint scans the
2752
+ * `messages` table for the window and synthesizes a webhook delivery
2753
+ * for any message whose `message.sent` / `message.delivered` /
2754
+ * `message.failed` event has not been successfully delivered yet.
2755
+ *
2756
+ * Synthesized events have fresh IDs — your endpoint should dedupe by
2757
+ * `event.data.object.id` (the message ID).
2758
+ *
2759
+ * Rejects with HTTP 409 if the circuit is currently open — call
2760
+ * {@link WebhooksResource.resetCircuit} first.
2761
+ *
2762
+ * @param id - Webhook ID
2763
+ * @param options - Window and filter options
2764
+ * @returns Counts grouped by event type plus the new delivery IDs
2765
+ *
2766
+ * @example
2767
+ * ```typescript
2768
+ * const result = await sendly.webhooks.backfill('whk_xxx', {
2769
+ * since: '2026-05-01T00:00:00Z',
2770
+ * eventTypes: ['message.delivered', 'message.failed'],
2771
+ * });
2772
+ * console.log(`Synthesized ${result.synthesized} events`, result.byType);
2773
+ * ```
2774
+ */
2775
+ backfill(id: string, options?: WebhookBackfillOptions): Promise<WebhookBackfillResult>;
2584
2776
  /**
2585
2777
  * Rotate the webhook signing secret
2586
2778
  *
@@ -3459,6 +3651,77 @@ declare class ContactsResource {
3459
3651
  */
3460
3652
  delete(id: string): Promise<void>;
3461
3653
  import(request: ImportContactsRequest): Promise<ImportContactsResponse>;
3654
+ /**
3655
+ * Clear the invalid flag on a contact so future campaigns include it again.
3656
+ *
3657
+ * Contacts get auto-flagged as invalid when a send fails with a terminal
3658
+ * bad-number error (landline, invalid number) or when a carrier lookup
3659
+ * reports they can't receive SMS. Use this when you disagree with the
3660
+ * auto-flag — e.g. the recipient ported from a landline to mobile.
3661
+ *
3662
+ * @param id - Contact ID
3663
+ * @returns The contact with the flag cleared
3664
+ *
3665
+ * @example
3666
+ * ```typescript
3667
+ * const contact = await sendly.contacts.markValid('cnt_xxx');
3668
+ * console.log(contact.invalidReason); // null
3669
+ * ```
3670
+ */
3671
+ markValid(id: string): Promise<Contact>;
3672
+ /**
3673
+ * Clear the invalid flag on many contacts at once — the escape hatch
3674
+ * for when auto-flag misclassifies at scale. Pass either an explicit
3675
+ * id array (up to 10,000 per call) OR a `listId`, not both. Foreign
3676
+ * ids silently no-op via the per-organization filter.
3677
+ *
3678
+ * @param options - `{ ids }` or `{ listId }`
3679
+ * @returns `{ cleared }` — number of contacts whose flag was actually
3680
+ * cleared. Already-clean contacts and foreign ids don't count.
3681
+ *
3682
+ * @example
3683
+ * ```typescript
3684
+ * // Clear a specific set of ids
3685
+ * const { cleared } = await sendly.contacts.bulkMarkValid({
3686
+ * ids: ['cnt_abc', 'cnt_def', 'cnt_ghi'],
3687
+ * });
3688
+ *
3689
+ * // Clear every flagged member of a list
3690
+ * await sendly.contacts.bulkMarkValid({ listId: 'lst_xxx' });
3691
+ * ```
3692
+ */
3693
+ bulkMarkValid(options: BulkMarkValidOptions): Promise<BulkMarkValidResponse>;
3694
+ /**
3695
+ * Trigger a background carrier lookup across your contacts. Landlines and
3696
+ * other non-SMS-capable numbers are auto-excluded from future campaigns.
3697
+ *
3698
+ * The lookup runs asynchronously and takes 1–5 minutes depending on list
3699
+ * size. Results populate the `lineType`, `carrierName`, and `invalidReason`
3700
+ * fields on affected contacts — fetch the contact again after a few minutes
3701
+ * to see updated state.
3702
+ *
3703
+ * Idempotent: re-triggering while a lookup is already running for the same
3704
+ * scope is a no-op.
3705
+ *
3706
+ * @param options - Optional: scope to a single list, or force re-check
3707
+ * @returns Acknowledgement
3708
+ *
3709
+ * @example
3710
+ * ```typescript
3711
+ * // Check all un-checked contacts
3712
+ * await sendly.contacts.checkNumbers();
3713
+ *
3714
+ * // Check a specific list only
3715
+ * await sendly.contacts.checkNumbers({ listId: 'lst_xxx' });
3716
+ *
3717
+ * // Re-check contacts even if previously looked up
3718
+ * await sendly.contacts.checkNumbers({ force: true });
3719
+ * ```
3720
+ */
3721
+ checkNumbers(options?: {
3722
+ listId?: string;
3723
+ force?: boolean;
3724
+ }): Promise<CheckNumbersResponse>;
3462
3725
  private transformContact;
3463
3726
  }
3464
3727
  /**
@@ -4480,4 +4743,4 @@ declare class Webhooks {
4480
4743
  */
4481
4744
  type WebhookMessageData = WebhookMessageObject;
4482
4745
 
4483
- export { ALL_SUPPORTED_COUNTRIES, type Account, type AddLabelsRequest, type AnalyticsOverview, type AnalyticsPeriod, type ApiErrorResponse, type ApiKey, AuthenticationError, type AutoTopUpSettings, type BatchListResponse, type BatchMessageItem, type BatchMessageRequest, type BatchMessageResponse, type BatchMessageResult, type BatchStatus, type BillingBreakdown, type BillingBreakdownOptions, type BulkProvisionResult, type BulkProvisionResultItem, type BulkProvisionWorkspace, CREDITS_PER_SMS, type Campaign, type CampaignListResponse, type CampaignPreview, type CampaignStatus, type CancelledMessageResponse, type CheckVerificationRequest, type CheckVerificationResponse, type CircuitState, type Contact, type ContactList, type ContactListResponse, type ContactListsResponse, type Conversation, type ConversationListResponse, type ConversationStatus, type ConversationWithMessages, type CreateCampaignRequest, type CreateContactListRequest, type CreateContactRequest, type CreateDraftRequest, type CreateKeyOptions, type CreateLabelRequest, type CreateOptInPageOptions, type CreateOptInPageResult, type CreateTemplateRequest, type CreateVerifySessionRequest, type CreateWebhookOptions, type CreateWorkspaceOptions, type CreatedApiKey, type CreditAnalytics, type CreditAnalyticsDataPoint, type CreditTransaction, type Credits, type DeliveryAnalyticsItem, type DeliveryStatus, type DnsRecord, type DraftListResponse, type DraftStatus, type EnterpriseAccount, type EnterpriseWebhook, type EnterpriseWebhookTestResult, type EnterpriseWorkspace, type EnterpriseWorkspaceDetail, type EnterpriseWorkspaceSummary, type GenerateBusinessPageOptions, type GenerateBusinessPageResponse, type GetConversationOptions, type ImportContactItem, type ImportContactsError, type ImportContactsRequest, type ImportContactsResponse, InsufficientCreditsError, type Invitation, type Label, type LabelListResponse, type ListBatchesOptions, type ListCampaignsOptions, type ListContactsOptions, type ListConversationsOptions, type ListDraftsOptions, type ListMessagesOptions, type ListScheduledMessagesOptions, type ListVerificationsOptions, type MediaFile, type MediaUploadOptions, type Message, type MessageAnalytics, type MessageAnalyticsDataPoint, type MessageDraft, type MessageListResponse, type MessageStatus, type MessageType, NetworkError, NotFoundError, type OptInPage, type PricingTier, type ProvisionWorkspaceOptions, type ProvisionWorkspaceResult, type QuotaSettings, RateLimitError, type RateLimitInfo, type ReplyToConversationRequest, type ResumeWorkspaceResult, SANDBOX_TEST_NUMBERS, SUPPORTED_COUNTRIES, type ScheduleCampaignRequest, type ScheduleMessageRequest, type ScheduledMessage, type ScheduledMessageListResponse, type ScheduledMessageStatus, type SendInvitationOptions, type SendMessageRequest, type SendVerificationRequest, type SendVerificationResponse, type SenderType, Sendly, type SendlyConfig, SendlyError, type SendlyErrorCode, type SetCustomDomainResult, type SetWorkspaceWebhookOptions, type SetWorkspaceWebhookResult, type SuggestRepliesResponse, type SuggestedReply, type SuspendWorkspaceOptions, type SuspendWorkspaceResult, type Template, type TemplateListResponse, type TemplatePreview, type TemplateStatus, type TemplateVariable, TimeoutError, type TransferCreditsOptions, type TransferCreditsResult, type UpdateAutoTopUpOptions, type UpdateCampaignRequest, type UpdateContactListRequest, type UpdateContactRequest, type UpdateConversationRequest, type UpdateDraftRequest, type UpdateOptInPageOptions, type UpdateQuotaOptions, type UpdateTemplateRequest, type UpdateWebhookOptions, type ValidateSessionTokenRequest, type ValidateSessionTokenResponse, ValidationError, type Verification, type VerificationDeliveryStatus, type VerificationListResponse, type VerificationStatus, type VerifySession, type VerifySessionStatus, type Webhook, type WebhookCreatedResponse, type WebhookDelivery, type WebhookEvent, type WebhookEventType, type WebhookMessageData, type WebhookMessageStatus, type WebhookSecretRotation, WebhookSignatureError, type WebhookTestResult, Webhooks, type WorkspaceBillingItem, type WorkspaceCredits, type WorkspaceWebhook, calculateSegments, Sendly as default, generateWebhookSignature, getCountryFromPhone, isCountrySupported, parseWebhookEvent, validateMessageText, validatePhoneNumber, validateSenderId, verifyWebhookSignature };
4746
+ export { ALL_SUPPORTED_COUNTRIES, type Account, type AddLabelsRequest, type AnalyticsOverview, type AnalyticsPeriod, type ApiErrorResponse, type ApiKey, AuthenticationError, type AutoTopUpSettings, type BatchListResponse, type BatchMessageItem, type BatchMessageRequest, type BatchMessageResponse, type BatchMessageResult, type BatchStatus, type BillingBreakdown, type BillingBreakdownOptions, type BulkMarkValidOptions, type BulkMarkValidResponse, type BulkProvisionResult, type BulkProvisionResultItem, type BulkProvisionWorkspace, CREDITS_PER_SMS, type Campaign, type CampaignListResponse, type CampaignPreview, type CampaignStatus, type CancelledMessageResponse, type CheckNumbersResponse, type CheckVerificationRequest, type CheckVerificationResponse, type CircuitState, type Contact, type ContactList, type ContactListResponse, type ContactListsResponse, type Conversation, type ConversationListResponse, type ConversationStatus, type ConversationWithMessages, type CreateCampaignRequest, type CreateContactListRequest, type CreateContactRequest, type CreateDraftRequest, type CreateKeyOptions, type CreateLabelRequest, type CreateOptInPageOptions, type CreateOptInPageResult, type CreateTemplateRequest, type CreateVerifySessionRequest, type CreateWebhookOptions, type CreateWorkspaceOptions, type CreatedApiKey, type CreditAnalytics, type CreditAnalyticsDataPoint, type CreditTransaction, type Credits, type DeliveryAnalyticsItem, type DeliveryStatus, type DnsRecord, type DraftListResponse, type DraftStatus, type EnterpriseAccount, type EnterpriseWebhook, type EnterpriseWebhookTestResult, type EnterpriseWorkspace, type EnterpriseWorkspaceDetail, type EnterpriseWorkspaceSummary, type GenerateBusinessPageOptions, type GenerateBusinessPageResponse, type GetConversationOptions, type ImportContactItem, type ImportContactsError, type ImportContactsRequest, type ImportContactsResponse, InsufficientCreditsError, type Invitation, type Label, type LabelListResponse, type ListBatchesOptions, type ListCampaignsOptions, type ListContactsOptions, type ListConversationsOptions, type ListDraftsOptions, type ListHealthEventSource, type ListMessagesOptions, type ListScheduledMessagesOptions, type ListVerificationsOptions, type MediaFile, type MediaUploadOptions, type Message, type MessageAnalytics, type MessageAnalyticsDataPoint, type MessageDraft, type MessageListResponse, type MessageStatus, type MessageType, NetworkError, NotFoundError, type OptInPage, type PricingTier, type ProvisionWorkspaceOptions, type ProvisionWorkspaceResult, type QuotaSettings, RateLimitError, type RateLimitInfo, type ReplyToConversationRequest, type ResumeWorkspaceResult, SANDBOX_TEST_NUMBERS, SUPPORTED_COUNTRIES, type ScheduleCampaignRequest, type ScheduleMessageRequest, type ScheduledMessage, type ScheduledMessageListResponse, type ScheduledMessageStatus, type SendInvitationOptions, type SendMessageRequest, type SendVerificationRequest, type SendVerificationResponse, type SenderType, Sendly, type SendlyConfig, SendlyError, type SendlyErrorCode, type SetCustomDomainResult, type SetWorkspaceWebhookOptions, type SetWorkspaceWebhookResult, type SuggestRepliesResponse, type SuggestedReply, type SuspendWorkspaceOptions, type SuspendWorkspaceResult, type Template, type TemplateListResponse, type TemplatePreview, type TemplateStatus, type TemplateVariable, TimeoutError, type TransferCreditsOptions, type TransferCreditsResult, type UpdateAutoTopUpOptions, type UpdateCampaignRequest, type UpdateContactListRequest, type UpdateContactRequest, type UpdateConversationRequest, type UpdateDraftRequest, type UpdateOptInPageOptions, type UpdateQuotaOptions, type UpdateTemplateRequest, type UpdateWebhookOptions, type ValidateSessionTokenRequest, type ValidateSessionTokenResponse, ValidationError, type Verification, type VerificationDeliveryStatus, type VerificationListResponse, type VerificationStatus, type VerifySession, type VerifySessionStatus, type Webhook, type WebhookCreatedResponse, type WebhookDelivery, type WebhookEvent, type WebhookEventType, type WebhookMessageData, type WebhookMessageStatus, type WebhookSecretRotation, WebhookSignatureError, type WebhookTestResult, Webhooks, type WorkspaceBillingItem, type WorkspaceCredits, type WorkspaceWebhook, calculateSegments, Sendly as default, generateWebhookSignature, getCountryFromPhone, isCountrySupported, parseWebhookEvent, validateMessageText, validatePhoneNumber, validateSenderId, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -877,7 +877,12 @@ declare const ALL_SUPPORTED_COUNTRIES: string[];
877
877
  /**
878
878
  * Webhook event types
879
879
  */
880
- type WebhookEventType = "message.sent" | "message.delivered" | "message.failed" | "message.bounced" | "message.retrying" | "message.queued" | "message.received" | "message.opt_out" | "message.opt_in" | "verification.created" | "verification.delivered" | "verification.verified" | "verification.expired" | "verification.failed" | "verification.resent" | "verification.delivery_failed";
880
+ type WebhookEventType = "message.sent" | "message.delivered" | "message.failed" | "message.bounced" | "message.retrying" | "message.queued" | "message.received" | "message.opt_out" | "message.opt_in" | "verification.created" | "verification.delivered" | "verification.verified" | "verification.expired" | "verification.failed" | "verification.resent" | "verification.delivery_failed" | "contact.auto_flagged" | "contact.marked_valid" | "contacts.lookup_completed" | "contacts.bulk_marked_valid";
881
+ /**
882
+ * Source of a list-health event. Frozen enum — new values will be
883
+ * added in minor SDK versions, never removed.
884
+ */
885
+ type ListHealthEventSource = "send_failure" | "carrier_lookup" | "user_action" | "bulk_mark_valid";
881
886
  /**
882
887
  * Webhook mode - filters which events are delivered
883
888
  * - "all": Receives all events (sandbox + production)
@@ -973,6 +978,72 @@ interface UpdateWebhookOptions {
973
978
  /** Custom metadata */
974
979
  metadata?: Record<string, unknown>;
975
980
  }
981
+ /**
982
+ * Options for replaying webhook deliveries
983
+ */
984
+ interface WebhookRedeliverOptions {
985
+ /** Earliest delivery created_at to consider, ISO-8601 (default: now − 24h) */
986
+ since?: string;
987
+ /** Latest delivery created_at to consider, ISO-8601 (default: now) */
988
+ until?: string;
989
+ /** Filter by event type (default: all) */
990
+ eventTypes?: WebhookEventType[];
991
+ /** Replay deliveries in any of these statuses (default: ['failed', 'cancelled']) */
992
+ statuses?: DeliveryStatus[];
993
+ /** Maximum number of deliveries to requeue (default: 1000, max 10000) */
994
+ limit?: number;
995
+ }
996
+ /**
997
+ * Result of replaying webhook deliveries
998
+ */
999
+ interface WebhookRedeliverResult {
1000
+ message: string;
1001
+ /** Number of deliveries that were re-queued */
1002
+ requeued: number;
1003
+ /** Number of deliveries that failed to re-queue */
1004
+ skipped: number;
1005
+ /** True if the matching set was larger than `limit` */
1006
+ truncated: boolean;
1007
+ /** Total number of matching deliveries before the limit was applied */
1008
+ windowSize: number;
1009
+ /** IDs of the new delivery records created by the replay */
1010
+ deliveryIds: string[];
1011
+ since: string;
1012
+ until: string;
1013
+ limit: number;
1014
+ }
1015
+ /**
1016
+ * Options for backfilling missed webhook deliveries from the message log
1017
+ */
1018
+ interface WebhookBackfillOptions {
1019
+ /** Earliest message created_at to consider, ISO-8601 (default: now − 24h) */
1020
+ since?: string;
1021
+ /** Latest message created_at to consider, ISO-8601 (default: now) */
1022
+ until?: string;
1023
+ /** Filter by event type (default: subscribed message events) */
1024
+ eventTypes?: WebhookEventType[];
1025
+ /** Maximum number of events to synthesize (default: 1000, max 10000) */
1026
+ limit?: number;
1027
+ }
1028
+ /**
1029
+ * Result of backfilling missed webhook deliveries
1030
+ */
1031
+ interface WebhookBackfillResult {
1032
+ message: string;
1033
+ /** Number of deliveries synthesized and dispatched */
1034
+ synthesized: number;
1035
+ /** Synthesized count grouped by event type */
1036
+ byType: Record<string, number>;
1037
+ /** True if there were more eligible events than `limit` */
1038
+ truncated: boolean;
1039
+ /** Total number of messages scanned for the window */
1040
+ candidatesScanned: number;
1041
+ /** IDs of the new delivery records */
1042
+ deliveryIds: string[];
1043
+ since: string;
1044
+ until: string;
1045
+ limit: number;
1046
+ }
976
1047
  /**
977
1048
  * A webhook delivery attempt
978
1049
  */
@@ -1527,6 +1598,39 @@ interface Contact {
1527
1598
  * Whether the contact has opted out
1528
1599
  */
1529
1600
  optedOut?: boolean;
1601
+ /**
1602
+ * Carrier-reported line type for this number. One of: `mobile`, `voip`,
1603
+ * `toll free`, `fixed line`, `fixed line or mobile`, `pager`, `voicemail`,
1604
+ * `shared cost`, `premium rate`, `uan`, `personal number`, `unknown`.
1605
+ * Populated after a carrier lookup (either automatic or via checkNumbers).
1606
+ */
1607
+ lineType?: string | null;
1608
+ /**
1609
+ * Carrier name reported by the lookup (e.g., "AT&T", "Verizon").
1610
+ */
1611
+ carrierName?: string | null;
1612
+ /**
1613
+ * When the carrier lookup last ran for this contact.
1614
+ */
1615
+ lineTypeCheckedAt?: string | null;
1616
+ /**
1617
+ * Reason this contact is excluded from future campaigns. One of:
1618
+ * `landline`, `invalid_number`, `non_sms_capable`. Set automatically
1619
+ * after terminal send failures or by a carrier lookup. Clear it with
1620
+ * `contacts.markValid(id)`.
1621
+ */
1622
+ invalidReason?: string | null;
1623
+ /**
1624
+ * When the invalid flag was set.
1625
+ */
1626
+ invalidatedAt?: string | null;
1627
+ /**
1628
+ * When a user manually cleared an auto-flag on this contact. Carrier
1629
+ * re-checks that would re-flag the contact as invalid respect this
1630
+ * timestamp and leave the contact clean, so your manual decisions
1631
+ * survive future lookups.
1632
+ */
1633
+ userMarkedValidAt?: string | null;
1530
1634
  /**
1531
1635
  * When the contact was created
1532
1636
  */
@@ -1543,6 +1647,38 @@ interface Contact {
1543
1647
  name: string;
1544
1648
  }>;
1545
1649
  }
1650
+ /**
1651
+ * Response from triggering a bulk carrier lookup via `contacts.checkNumbers()`.
1652
+ */
1653
+ interface CheckNumbersResponse {
1654
+ success: boolean;
1655
+ /**
1656
+ * True if a carrier lookup for this scope was already running when you
1657
+ * called this. The dashboard renders an "already in progress" toast when
1658
+ * it sees this flag; server-side integrators can treat it as a soft
1659
+ * no-op and wait for `contacts.lookup_completed` webhook.
1660
+ */
1661
+ alreadyRunning?: boolean;
1662
+ message?: string;
1663
+ }
1664
+ /**
1665
+ * Scope for `contacts.bulkMarkValid()`. Pass either an explicit id array
1666
+ * (up to 10,000 per call) OR a `listId` — not both. Foreign ids silently
1667
+ * no-op via the per-org filter.
1668
+ */
1669
+ interface BulkMarkValidOptions {
1670
+ /** Explicit contact ids to clear (max 10,000). */
1671
+ ids?: string[];
1672
+ /** Clear every flagged member of this list. */
1673
+ listId?: string;
1674
+ }
1675
+ /**
1676
+ * Response from `contacts.bulkMarkValid()`.
1677
+ */
1678
+ interface BulkMarkValidResponse {
1679
+ /** Number of contacts whose invalid flag was actually cleared. */
1680
+ cleared: number;
1681
+ }
1546
1682
  /**
1547
1683
  * Request to create a contact
1548
1684
  */
@@ -2581,6 +2717,62 @@ declare class WebhooksResource {
2581
2717
  message: string;
2582
2718
  webhook: Webhook;
2583
2719
  }>;
2720
+ /**
2721
+ * Replay failed or cancelled webhook deliveries from the audit log.
2722
+ *
2723
+ * Use after a customer endpoint has recovered from an outage to re-fire
2724
+ * deliveries that we recorded but couldn't deliver. Each replay creates
2725
+ * a new delivery row preserving the original `event_id` so customers can
2726
+ * dedupe.
2727
+ *
2728
+ * Rejects with HTTP 409 if the circuit is currently open — call
2729
+ * {@link WebhooksResource.resetCircuit} first.
2730
+ *
2731
+ * @param id - Webhook ID
2732
+ * @param options - Window and filter options
2733
+ * @returns Counts of requeued deliveries plus the new delivery IDs
2734
+ *
2735
+ * @example
2736
+ * ```typescript
2737
+ * await sendly.webhooks.resetCircuit('whk_xxx');
2738
+ * const result = await sendly.webhooks.redeliver('whk_xxx', {
2739
+ * since: '2026-05-01T00:00:00Z',
2740
+ * eventTypes: ['message.delivered', 'message.failed'],
2741
+ * limit: 5000,
2742
+ * });
2743
+ * console.log(`Requeued ${result.requeued} deliveries`);
2744
+ * ```
2745
+ */
2746
+ redeliver(id: string, options?: WebhookRedeliverOptions): Promise<WebhookRedeliverResult>;
2747
+ /**
2748
+ * Backfill missed webhook events from the underlying message log.
2749
+ *
2750
+ * Use this when a circuit-breaker outage left events with no audit row
2751
+ * (the case `redeliver` cannot recover). The endpoint scans the
2752
+ * `messages` table for the window and synthesizes a webhook delivery
2753
+ * for any message whose `message.sent` / `message.delivered` /
2754
+ * `message.failed` event has not been successfully delivered yet.
2755
+ *
2756
+ * Synthesized events have fresh IDs — your endpoint should dedupe by
2757
+ * `event.data.object.id` (the message ID).
2758
+ *
2759
+ * Rejects with HTTP 409 if the circuit is currently open — call
2760
+ * {@link WebhooksResource.resetCircuit} first.
2761
+ *
2762
+ * @param id - Webhook ID
2763
+ * @param options - Window and filter options
2764
+ * @returns Counts grouped by event type plus the new delivery IDs
2765
+ *
2766
+ * @example
2767
+ * ```typescript
2768
+ * const result = await sendly.webhooks.backfill('whk_xxx', {
2769
+ * since: '2026-05-01T00:00:00Z',
2770
+ * eventTypes: ['message.delivered', 'message.failed'],
2771
+ * });
2772
+ * console.log(`Synthesized ${result.synthesized} events`, result.byType);
2773
+ * ```
2774
+ */
2775
+ backfill(id: string, options?: WebhookBackfillOptions): Promise<WebhookBackfillResult>;
2584
2776
  /**
2585
2777
  * Rotate the webhook signing secret
2586
2778
  *
@@ -3459,6 +3651,77 @@ declare class ContactsResource {
3459
3651
  */
3460
3652
  delete(id: string): Promise<void>;
3461
3653
  import(request: ImportContactsRequest): Promise<ImportContactsResponse>;
3654
+ /**
3655
+ * Clear the invalid flag on a contact so future campaigns include it again.
3656
+ *
3657
+ * Contacts get auto-flagged as invalid when a send fails with a terminal
3658
+ * bad-number error (landline, invalid number) or when a carrier lookup
3659
+ * reports they can't receive SMS. Use this when you disagree with the
3660
+ * auto-flag — e.g. the recipient ported from a landline to mobile.
3661
+ *
3662
+ * @param id - Contact ID
3663
+ * @returns The contact with the flag cleared
3664
+ *
3665
+ * @example
3666
+ * ```typescript
3667
+ * const contact = await sendly.contacts.markValid('cnt_xxx');
3668
+ * console.log(contact.invalidReason); // null
3669
+ * ```
3670
+ */
3671
+ markValid(id: string): Promise<Contact>;
3672
+ /**
3673
+ * Clear the invalid flag on many contacts at once — the escape hatch
3674
+ * for when auto-flag misclassifies at scale. Pass either an explicit
3675
+ * id array (up to 10,000 per call) OR a `listId`, not both. Foreign
3676
+ * ids silently no-op via the per-organization filter.
3677
+ *
3678
+ * @param options - `{ ids }` or `{ listId }`
3679
+ * @returns `{ cleared }` — number of contacts whose flag was actually
3680
+ * cleared. Already-clean contacts and foreign ids don't count.
3681
+ *
3682
+ * @example
3683
+ * ```typescript
3684
+ * // Clear a specific set of ids
3685
+ * const { cleared } = await sendly.contacts.bulkMarkValid({
3686
+ * ids: ['cnt_abc', 'cnt_def', 'cnt_ghi'],
3687
+ * });
3688
+ *
3689
+ * // Clear every flagged member of a list
3690
+ * await sendly.contacts.bulkMarkValid({ listId: 'lst_xxx' });
3691
+ * ```
3692
+ */
3693
+ bulkMarkValid(options: BulkMarkValidOptions): Promise<BulkMarkValidResponse>;
3694
+ /**
3695
+ * Trigger a background carrier lookup across your contacts. Landlines and
3696
+ * other non-SMS-capable numbers are auto-excluded from future campaigns.
3697
+ *
3698
+ * The lookup runs asynchronously and takes 1–5 minutes depending on list
3699
+ * size. Results populate the `lineType`, `carrierName`, and `invalidReason`
3700
+ * fields on affected contacts — fetch the contact again after a few minutes
3701
+ * to see updated state.
3702
+ *
3703
+ * Idempotent: re-triggering while a lookup is already running for the same
3704
+ * scope is a no-op.
3705
+ *
3706
+ * @param options - Optional: scope to a single list, or force re-check
3707
+ * @returns Acknowledgement
3708
+ *
3709
+ * @example
3710
+ * ```typescript
3711
+ * // Check all un-checked contacts
3712
+ * await sendly.contacts.checkNumbers();
3713
+ *
3714
+ * // Check a specific list only
3715
+ * await sendly.contacts.checkNumbers({ listId: 'lst_xxx' });
3716
+ *
3717
+ * // Re-check contacts even if previously looked up
3718
+ * await sendly.contacts.checkNumbers({ force: true });
3719
+ * ```
3720
+ */
3721
+ checkNumbers(options?: {
3722
+ listId?: string;
3723
+ force?: boolean;
3724
+ }): Promise<CheckNumbersResponse>;
3462
3725
  private transformContact;
3463
3726
  }
3464
3727
  /**
@@ -4480,4 +4743,4 @@ declare class Webhooks {
4480
4743
  */
4481
4744
  type WebhookMessageData = WebhookMessageObject;
4482
4745
 
4483
- export { ALL_SUPPORTED_COUNTRIES, type Account, type AddLabelsRequest, type AnalyticsOverview, type AnalyticsPeriod, type ApiErrorResponse, type ApiKey, AuthenticationError, type AutoTopUpSettings, type BatchListResponse, type BatchMessageItem, type BatchMessageRequest, type BatchMessageResponse, type BatchMessageResult, type BatchStatus, type BillingBreakdown, type BillingBreakdownOptions, type BulkProvisionResult, type BulkProvisionResultItem, type BulkProvisionWorkspace, CREDITS_PER_SMS, type Campaign, type CampaignListResponse, type CampaignPreview, type CampaignStatus, type CancelledMessageResponse, type CheckVerificationRequest, type CheckVerificationResponse, type CircuitState, type Contact, type ContactList, type ContactListResponse, type ContactListsResponse, type Conversation, type ConversationListResponse, type ConversationStatus, type ConversationWithMessages, type CreateCampaignRequest, type CreateContactListRequest, type CreateContactRequest, type CreateDraftRequest, type CreateKeyOptions, type CreateLabelRequest, type CreateOptInPageOptions, type CreateOptInPageResult, type CreateTemplateRequest, type CreateVerifySessionRequest, type CreateWebhookOptions, type CreateWorkspaceOptions, type CreatedApiKey, type CreditAnalytics, type CreditAnalyticsDataPoint, type CreditTransaction, type Credits, type DeliveryAnalyticsItem, type DeliveryStatus, type DnsRecord, type DraftListResponse, type DraftStatus, type EnterpriseAccount, type EnterpriseWebhook, type EnterpriseWebhookTestResult, type EnterpriseWorkspace, type EnterpriseWorkspaceDetail, type EnterpriseWorkspaceSummary, type GenerateBusinessPageOptions, type GenerateBusinessPageResponse, type GetConversationOptions, type ImportContactItem, type ImportContactsError, type ImportContactsRequest, type ImportContactsResponse, InsufficientCreditsError, type Invitation, type Label, type LabelListResponse, type ListBatchesOptions, type ListCampaignsOptions, type ListContactsOptions, type ListConversationsOptions, type ListDraftsOptions, type ListMessagesOptions, type ListScheduledMessagesOptions, type ListVerificationsOptions, type MediaFile, type MediaUploadOptions, type Message, type MessageAnalytics, type MessageAnalyticsDataPoint, type MessageDraft, type MessageListResponse, type MessageStatus, type MessageType, NetworkError, NotFoundError, type OptInPage, type PricingTier, type ProvisionWorkspaceOptions, type ProvisionWorkspaceResult, type QuotaSettings, RateLimitError, type RateLimitInfo, type ReplyToConversationRequest, type ResumeWorkspaceResult, SANDBOX_TEST_NUMBERS, SUPPORTED_COUNTRIES, type ScheduleCampaignRequest, type ScheduleMessageRequest, type ScheduledMessage, type ScheduledMessageListResponse, type ScheduledMessageStatus, type SendInvitationOptions, type SendMessageRequest, type SendVerificationRequest, type SendVerificationResponse, type SenderType, Sendly, type SendlyConfig, SendlyError, type SendlyErrorCode, type SetCustomDomainResult, type SetWorkspaceWebhookOptions, type SetWorkspaceWebhookResult, type SuggestRepliesResponse, type SuggestedReply, type SuspendWorkspaceOptions, type SuspendWorkspaceResult, type Template, type TemplateListResponse, type TemplatePreview, type TemplateStatus, type TemplateVariable, TimeoutError, type TransferCreditsOptions, type TransferCreditsResult, type UpdateAutoTopUpOptions, type UpdateCampaignRequest, type UpdateContactListRequest, type UpdateContactRequest, type UpdateConversationRequest, type UpdateDraftRequest, type UpdateOptInPageOptions, type UpdateQuotaOptions, type UpdateTemplateRequest, type UpdateWebhookOptions, type ValidateSessionTokenRequest, type ValidateSessionTokenResponse, ValidationError, type Verification, type VerificationDeliveryStatus, type VerificationListResponse, type VerificationStatus, type VerifySession, type VerifySessionStatus, type Webhook, type WebhookCreatedResponse, type WebhookDelivery, type WebhookEvent, type WebhookEventType, type WebhookMessageData, type WebhookMessageStatus, type WebhookSecretRotation, WebhookSignatureError, type WebhookTestResult, Webhooks, type WorkspaceBillingItem, type WorkspaceCredits, type WorkspaceWebhook, calculateSegments, Sendly as default, generateWebhookSignature, getCountryFromPhone, isCountrySupported, parseWebhookEvent, validateMessageText, validatePhoneNumber, validateSenderId, verifyWebhookSignature };
4746
+ export { ALL_SUPPORTED_COUNTRIES, type Account, type AddLabelsRequest, type AnalyticsOverview, type AnalyticsPeriod, type ApiErrorResponse, type ApiKey, AuthenticationError, type AutoTopUpSettings, type BatchListResponse, type BatchMessageItem, type BatchMessageRequest, type BatchMessageResponse, type BatchMessageResult, type BatchStatus, type BillingBreakdown, type BillingBreakdownOptions, type BulkMarkValidOptions, type BulkMarkValidResponse, type BulkProvisionResult, type BulkProvisionResultItem, type BulkProvisionWorkspace, CREDITS_PER_SMS, type Campaign, type CampaignListResponse, type CampaignPreview, type CampaignStatus, type CancelledMessageResponse, type CheckNumbersResponse, type CheckVerificationRequest, type CheckVerificationResponse, type CircuitState, type Contact, type ContactList, type ContactListResponse, type ContactListsResponse, type Conversation, type ConversationListResponse, type ConversationStatus, type ConversationWithMessages, type CreateCampaignRequest, type CreateContactListRequest, type CreateContactRequest, type CreateDraftRequest, type CreateKeyOptions, type CreateLabelRequest, type CreateOptInPageOptions, type CreateOptInPageResult, type CreateTemplateRequest, type CreateVerifySessionRequest, type CreateWebhookOptions, type CreateWorkspaceOptions, type CreatedApiKey, type CreditAnalytics, type CreditAnalyticsDataPoint, type CreditTransaction, type Credits, type DeliveryAnalyticsItem, type DeliveryStatus, type DnsRecord, type DraftListResponse, type DraftStatus, type EnterpriseAccount, type EnterpriseWebhook, type EnterpriseWebhookTestResult, type EnterpriseWorkspace, type EnterpriseWorkspaceDetail, type EnterpriseWorkspaceSummary, type GenerateBusinessPageOptions, type GenerateBusinessPageResponse, type GetConversationOptions, type ImportContactItem, type ImportContactsError, type ImportContactsRequest, type ImportContactsResponse, InsufficientCreditsError, type Invitation, type Label, type LabelListResponse, type ListBatchesOptions, type ListCampaignsOptions, type ListContactsOptions, type ListConversationsOptions, type ListDraftsOptions, type ListHealthEventSource, type ListMessagesOptions, type ListScheduledMessagesOptions, type ListVerificationsOptions, type MediaFile, type MediaUploadOptions, type Message, type MessageAnalytics, type MessageAnalyticsDataPoint, type MessageDraft, type MessageListResponse, type MessageStatus, type MessageType, NetworkError, NotFoundError, type OptInPage, type PricingTier, type ProvisionWorkspaceOptions, type ProvisionWorkspaceResult, type QuotaSettings, RateLimitError, type RateLimitInfo, type ReplyToConversationRequest, type ResumeWorkspaceResult, SANDBOX_TEST_NUMBERS, SUPPORTED_COUNTRIES, type ScheduleCampaignRequest, type ScheduleMessageRequest, type ScheduledMessage, type ScheduledMessageListResponse, type ScheduledMessageStatus, type SendInvitationOptions, type SendMessageRequest, type SendVerificationRequest, type SendVerificationResponse, type SenderType, Sendly, type SendlyConfig, SendlyError, type SendlyErrorCode, type SetCustomDomainResult, type SetWorkspaceWebhookOptions, type SetWorkspaceWebhookResult, type SuggestRepliesResponse, type SuggestedReply, type SuspendWorkspaceOptions, type SuspendWorkspaceResult, type Template, type TemplateListResponse, type TemplatePreview, type TemplateStatus, type TemplateVariable, TimeoutError, type TransferCreditsOptions, type TransferCreditsResult, type UpdateAutoTopUpOptions, type UpdateCampaignRequest, type UpdateContactListRequest, type UpdateContactRequest, type UpdateConversationRequest, type UpdateDraftRequest, type UpdateOptInPageOptions, type UpdateQuotaOptions, type UpdateTemplateRequest, type UpdateWebhookOptions, type ValidateSessionTokenRequest, type ValidateSessionTokenResponse, ValidationError, type Verification, type VerificationDeliveryStatus, type VerificationListResponse, type VerificationStatus, type VerifySession, type VerifySessionStatus, type Webhook, type WebhookCreatedResponse, type WebhookDelivery, type WebhookEvent, type WebhookEventType, type WebhookMessageData, type WebhookMessageStatus, type WebhookSecretRotation, WebhookSignatureError, type WebhookTestResult, Webhooks, type WorkspaceBillingItem, type WorkspaceCredits, type WorkspaceWebhook, calculateSegments, Sendly as default, generateWebhookSignature, getCountryFromPhone, isCountrySupported, parseWebhookEvent, validateMessageText, validatePhoneNumber, validateSenderId, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -1316,6 +1316,93 @@ var WebhooksResource = class {
1316
1316
  });
1317
1317
  return transformKeys(response);
1318
1318
  }
1319
+ /**
1320
+ * Replay failed or cancelled webhook deliveries from the audit log.
1321
+ *
1322
+ * Use after a customer endpoint has recovered from an outage to re-fire
1323
+ * deliveries that we recorded but couldn't deliver. Each replay creates
1324
+ * a new delivery row preserving the original `event_id` so customers can
1325
+ * dedupe.
1326
+ *
1327
+ * Rejects with HTTP 409 if the circuit is currently open — call
1328
+ * {@link WebhooksResource.resetCircuit} first.
1329
+ *
1330
+ * @param id - Webhook ID
1331
+ * @param options - Window and filter options
1332
+ * @returns Counts of requeued deliveries plus the new delivery IDs
1333
+ *
1334
+ * @example
1335
+ * ```typescript
1336
+ * await sendly.webhooks.resetCircuit('whk_xxx');
1337
+ * const result = await sendly.webhooks.redeliver('whk_xxx', {
1338
+ * since: '2026-05-01T00:00:00Z',
1339
+ * eventTypes: ['message.delivered', 'message.failed'],
1340
+ * limit: 5000,
1341
+ * });
1342
+ * console.log(`Requeued ${result.requeued} deliveries`);
1343
+ * ```
1344
+ */
1345
+ async redeliver(id, options = {}) {
1346
+ if (!id || !id.startsWith("whk_")) {
1347
+ throw new Error("Invalid webhook ID format");
1348
+ }
1349
+ const body = {};
1350
+ if (options.since !== void 0) body.since = options.since;
1351
+ if (options.until !== void 0) body.until = options.until;
1352
+ if (options.eventTypes !== void 0) body.event_types = options.eventTypes;
1353
+ if (options.statuses !== void 0) body.statuses = options.statuses;
1354
+ if (options.limit !== void 0) body.limit = options.limit;
1355
+ const response = await this.http.request({
1356
+ method: "POST",
1357
+ path: `/webhooks/${encodeURIComponent(id)}/redeliver`,
1358
+ body
1359
+ });
1360
+ return transformKeys(response);
1361
+ }
1362
+ /**
1363
+ * Backfill missed webhook events from the underlying message log.
1364
+ *
1365
+ * Use this when a circuit-breaker outage left events with no audit row
1366
+ * (the case `redeliver` cannot recover). The endpoint scans the
1367
+ * `messages` table for the window and synthesizes a webhook delivery
1368
+ * for any message whose `message.sent` / `message.delivered` /
1369
+ * `message.failed` event has not been successfully delivered yet.
1370
+ *
1371
+ * Synthesized events have fresh IDs — your endpoint should dedupe by
1372
+ * `event.data.object.id` (the message ID).
1373
+ *
1374
+ * Rejects with HTTP 409 if the circuit is currently open — call
1375
+ * {@link WebhooksResource.resetCircuit} first.
1376
+ *
1377
+ * @param id - Webhook ID
1378
+ * @param options - Window and filter options
1379
+ * @returns Counts grouped by event type plus the new delivery IDs
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * const result = await sendly.webhooks.backfill('whk_xxx', {
1384
+ * since: '2026-05-01T00:00:00Z',
1385
+ * eventTypes: ['message.delivered', 'message.failed'],
1386
+ * });
1387
+ * console.log(`Synthesized ${result.synthesized} events`, result.byType);
1388
+ * ```
1389
+ */
1390
+ async backfill(id, options = {}) {
1391
+ if (!id || !id.startsWith("whk_")) {
1392
+ throw new Error("Invalid webhook ID format");
1393
+ }
1394
+ const body = {};
1395
+ if (options.since !== void 0) body.since = options.since;
1396
+ if (options.until !== void 0) body.until = options.until;
1397
+ if (options.eventTypes !== void 0) body.event_types = options.eventTypes;
1398
+ if (options.limit !== void 0) body.limit = options.limit;
1399
+ const response = await this.http.request({
1400
+ method: "POST",
1401
+ path: `/webhooks/${encodeURIComponent(id)}/backfill`,
1402
+ body
1403
+ });
1404
+ return transformKeys(response);
1405
+ }
1319
1406
  /**
1320
1407
  * Rotate the webhook signing secret
1321
1408
  *
@@ -2610,16 +2697,118 @@ var ContactsResource = class {
2610
2697
  body
2611
2698
  });
2612
2699
  }
2700
+ /**
2701
+ * Clear the invalid flag on a contact so future campaigns include it again.
2702
+ *
2703
+ * Contacts get auto-flagged as invalid when a send fails with a terminal
2704
+ * bad-number error (landline, invalid number) or when a carrier lookup
2705
+ * reports they can't receive SMS. Use this when you disagree with the
2706
+ * auto-flag — e.g. the recipient ported from a landline to mobile.
2707
+ *
2708
+ * @param id - Contact ID
2709
+ * @returns The contact with the flag cleared
2710
+ *
2711
+ * @example
2712
+ * ```typescript
2713
+ * const contact = await sendly.contacts.markValid('cnt_xxx');
2714
+ * console.log(contact.invalidReason); // null
2715
+ * ```
2716
+ */
2717
+ async markValid(id) {
2718
+ const response = await this.http.request({
2719
+ method: "POST",
2720
+ path: `/contacts/${id}/mark-valid`
2721
+ });
2722
+ return this.transformContact(response);
2723
+ }
2724
+ /**
2725
+ * Clear the invalid flag on many contacts at once — the escape hatch
2726
+ * for when auto-flag misclassifies at scale. Pass either an explicit
2727
+ * id array (up to 10,000 per call) OR a `listId`, not both. Foreign
2728
+ * ids silently no-op via the per-organization filter.
2729
+ *
2730
+ * @param options - `{ ids }` or `{ listId }`
2731
+ * @returns `{ cleared }` — number of contacts whose flag was actually
2732
+ * cleared. Already-clean contacts and foreign ids don't count.
2733
+ *
2734
+ * @example
2735
+ * ```typescript
2736
+ * // Clear a specific set of ids
2737
+ * const { cleared } = await sendly.contacts.bulkMarkValid({
2738
+ * ids: ['cnt_abc', 'cnt_def', 'cnt_ghi'],
2739
+ * });
2740
+ *
2741
+ * // Clear every flagged member of a list
2742
+ * await sendly.contacts.bulkMarkValid({ listId: 'lst_xxx' });
2743
+ * ```
2744
+ */
2745
+ async bulkMarkValid(options) {
2746
+ if (!options.ids && !options.listId) {
2747
+ throw new Error("bulkMarkValid requires either 'ids' or 'listId'");
2748
+ }
2749
+ if (options.ids && options.listId) {
2750
+ throw new Error("bulkMarkValid accepts 'ids' OR 'listId', not both");
2751
+ }
2752
+ return this.http.request({
2753
+ method: "POST",
2754
+ path: "/contacts/bulk-mark-valid",
2755
+ body: options.ids ? { ids: options.ids } : { listId: options.listId }
2756
+ });
2757
+ }
2758
+ /**
2759
+ * Trigger a background carrier lookup across your contacts. Landlines and
2760
+ * other non-SMS-capable numbers are auto-excluded from future campaigns.
2761
+ *
2762
+ * The lookup runs asynchronously and takes 1–5 minutes depending on list
2763
+ * size. Results populate the `lineType`, `carrierName`, and `invalidReason`
2764
+ * fields on affected contacts — fetch the contact again after a few minutes
2765
+ * to see updated state.
2766
+ *
2767
+ * Idempotent: re-triggering while a lookup is already running for the same
2768
+ * scope is a no-op.
2769
+ *
2770
+ * @param options - Optional: scope to a single list, or force re-check
2771
+ * @returns Acknowledgement
2772
+ *
2773
+ * @example
2774
+ * ```typescript
2775
+ * // Check all un-checked contacts
2776
+ * await sendly.contacts.checkNumbers();
2777
+ *
2778
+ * // Check a specific list only
2779
+ * await sendly.contacts.checkNumbers({ listId: 'lst_xxx' });
2780
+ *
2781
+ * // Re-check contacts even if previously looked up
2782
+ * await sendly.contacts.checkNumbers({ force: true });
2783
+ * ```
2784
+ */
2785
+ async checkNumbers(options = {}) {
2786
+ return this.http.request({
2787
+ method: "POST",
2788
+ path: "/contacts/lookup",
2789
+ body: {
2790
+ listId: options.listId ?? null,
2791
+ force: options.force ?? false
2792
+ }
2793
+ });
2794
+ }
2613
2795
  transformContact(raw) {
2796
+ const r = raw;
2614
2797
  return {
2615
2798
  id: raw.id,
2616
- phoneNumber: raw.phone_number,
2799
+ phoneNumber: raw.phone_number ?? r.phoneNumber,
2617
2800
  name: raw.name,
2618
2801
  email: raw.email,
2619
2802
  metadata: raw.metadata,
2620
- optedOut: raw.opted_out,
2621
- createdAt: raw.created_at,
2622
- updatedAt: raw.updated_at,
2803
+ optedOut: raw.opted_out ?? r.optedOut,
2804
+ lineType: raw.line_type ?? r.lineType ?? null,
2805
+ carrierName: raw.carrier_name ?? r.carrierName ?? null,
2806
+ lineTypeCheckedAt: raw.line_type_checked_at ?? r.lineTypeCheckedAt ?? null,
2807
+ invalidReason: raw.invalid_reason ?? r.invalidReason ?? null,
2808
+ invalidatedAt: raw.invalidated_at ?? r.invalidatedAt ?? null,
2809
+ userMarkedValidAt: raw.user_marked_valid_at ?? r.userMarkedValidAt ?? null,
2810
+ createdAt: raw.created_at ?? r.createdAt,
2811
+ updatedAt: raw.updated_at ?? r.updatedAt,
2623
2812
  lists: raw.lists?.map((l) => ({ id: l.id, name: l.name }))
2624
2813
  };
2625
2814
  }
package/dist/index.mjs CHANGED
@@ -1256,6 +1256,93 @@ var WebhooksResource = class {
1256
1256
  });
1257
1257
  return transformKeys(response);
1258
1258
  }
1259
+ /**
1260
+ * Replay failed or cancelled webhook deliveries from the audit log.
1261
+ *
1262
+ * Use after a customer endpoint has recovered from an outage to re-fire
1263
+ * deliveries that we recorded but couldn't deliver. Each replay creates
1264
+ * a new delivery row preserving the original `event_id` so customers can
1265
+ * dedupe.
1266
+ *
1267
+ * Rejects with HTTP 409 if the circuit is currently open — call
1268
+ * {@link WebhooksResource.resetCircuit} first.
1269
+ *
1270
+ * @param id - Webhook ID
1271
+ * @param options - Window and filter options
1272
+ * @returns Counts of requeued deliveries plus the new delivery IDs
1273
+ *
1274
+ * @example
1275
+ * ```typescript
1276
+ * await sendly.webhooks.resetCircuit('whk_xxx');
1277
+ * const result = await sendly.webhooks.redeliver('whk_xxx', {
1278
+ * since: '2026-05-01T00:00:00Z',
1279
+ * eventTypes: ['message.delivered', 'message.failed'],
1280
+ * limit: 5000,
1281
+ * });
1282
+ * console.log(`Requeued ${result.requeued} deliveries`);
1283
+ * ```
1284
+ */
1285
+ async redeliver(id, options = {}) {
1286
+ if (!id || !id.startsWith("whk_")) {
1287
+ throw new Error("Invalid webhook ID format");
1288
+ }
1289
+ const body = {};
1290
+ if (options.since !== void 0) body.since = options.since;
1291
+ if (options.until !== void 0) body.until = options.until;
1292
+ if (options.eventTypes !== void 0) body.event_types = options.eventTypes;
1293
+ if (options.statuses !== void 0) body.statuses = options.statuses;
1294
+ if (options.limit !== void 0) body.limit = options.limit;
1295
+ const response = await this.http.request({
1296
+ method: "POST",
1297
+ path: `/webhooks/${encodeURIComponent(id)}/redeliver`,
1298
+ body
1299
+ });
1300
+ return transformKeys(response);
1301
+ }
1302
+ /**
1303
+ * Backfill missed webhook events from the underlying message log.
1304
+ *
1305
+ * Use this when a circuit-breaker outage left events with no audit row
1306
+ * (the case `redeliver` cannot recover). The endpoint scans the
1307
+ * `messages` table for the window and synthesizes a webhook delivery
1308
+ * for any message whose `message.sent` / `message.delivered` /
1309
+ * `message.failed` event has not been successfully delivered yet.
1310
+ *
1311
+ * Synthesized events have fresh IDs — your endpoint should dedupe by
1312
+ * `event.data.object.id` (the message ID).
1313
+ *
1314
+ * Rejects with HTTP 409 if the circuit is currently open — call
1315
+ * {@link WebhooksResource.resetCircuit} first.
1316
+ *
1317
+ * @param id - Webhook ID
1318
+ * @param options - Window and filter options
1319
+ * @returns Counts grouped by event type plus the new delivery IDs
1320
+ *
1321
+ * @example
1322
+ * ```typescript
1323
+ * const result = await sendly.webhooks.backfill('whk_xxx', {
1324
+ * since: '2026-05-01T00:00:00Z',
1325
+ * eventTypes: ['message.delivered', 'message.failed'],
1326
+ * });
1327
+ * console.log(`Synthesized ${result.synthesized} events`, result.byType);
1328
+ * ```
1329
+ */
1330
+ async backfill(id, options = {}) {
1331
+ if (!id || !id.startsWith("whk_")) {
1332
+ throw new Error("Invalid webhook ID format");
1333
+ }
1334
+ const body = {};
1335
+ if (options.since !== void 0) body.since = options.since;
1336
+ if (options.until !== void 0) body.until = options.until;
1337
+ if (options.eventTypes !== void 0) body.event_types = options.eventTypes;
1338
+ if (options.limit !== void 0) body.limit = options.limit;
1339
+ const response = await this.http.request({
1340
+ method: "POST",
1341
+ path: `/webhooks/${encodeURIComponent(id)}/backfill`,
1342
+ body
1343
+ });
1344
+ return transformKeys(response);
1345
+ }
1259
1346
  /**
1260
1347
  * Rotate the webhook signing secret
1261
1348
  *
@@ -2550,16 +2637,118 @@ var ContactsResource = class {
2550
2637
  body
2551
2638
  });
2552
2639
  }
2640
+ /**
2641
+ * Clear the invalid flag on a contact so future campaigns include it again.
2642
+ *
2643
+ * Contacts get auto-flagged as invalid when a send fails with a terminal
2644
+ * bad-number error (landline, invalid number) or when a carrier lookup
2645
+ * reports they can't receive SMS. Use this when you disagree with the
2646
+ * auto-flag — e.g. the recipient ported from a landline to mobile.
2647
+ *
2648
+ * @param id - Contact ID
2649
+ * @returns The contact with the flag cleared
2650
+ *
2651
+ * @example
2652
+ * ```typescript
2653
+ * const contact = await sendly.contacts.markValid('cnt_xxx');
2654
+ * console.log(contact.invalidReason); // null
2655
+ * ```
2656
+ */
2657
+ async markValid(id) {
2658
+ const response = await this.http.request({
2659
+ method: "POST",
2660
+ path: `/contacts/${id}/mark-valid`
2661
+ });
2662
+ return this.transformContact(response);
2663
+ }
2664
+ /**
2665
+ * Clear the invalid flag on many contacts at once — the escape hatch
2666
+ * for when auto-flag misclassifies at scale. Pass either an explicit
2667
+ * id array (up to 10,000 per call) OR a `listId`, not both. Foreign
2668
+ * ids silently no-op via the per-organization filter.
2669
+ *
2670
+ * @param options - `{ ids }` or `{ listId }`
2671
+ * @returns `{ cleared }` — number of contacts whose flag was actually
2672
+ * cleared. Already-clean contacts and foreign ids don't count.
2673
+ *
2674
+ * @example
2675
+ * ```typescript
2676
+ * // Clear a specific set of ids
2677
+ * const { cleared } = await sendly.contacts.bulkMarkValid({
2678
+ * ids: ['cnt_abc', 'cnt_def', 'cnt_ghi'],
2679
+ * });
2680
+ *
2681
+ * // Clear every flagged member of a list
2682
+ * await sendly.contacts.bulkMarkValid({ listId: 'lst_xxx' });
2683
+ * ```
2684
+ */
2685
+ async bulkMarkValid(options) {
2686
+ if (!options.ids && !options.listId) {
2687
+ throw new Error("bulkMarkValid requires either 'ids' or 'listId'");
2688
+ }
2689
+ if (options.ids && options.listId) {
2690
+ throw new Error("bulkMarkValid accepts 'ids' OR 'listId', not both");
2691
+ }
2692
+ return this.http.request({
2693
+ method: "POST",
2694
+ path: "/contacts/bulk-mark-valid",
2695
+ body: options.ids ? { ids: options.ids } : { listId: options.listId }
2696
+ });
2697
+ }
2698
+ /**
2699
+ * Trigger a background carrier lookup across your contacts. Landlines and
2700
+ * other non-SMS-capable numbers are auto-excluded from future campaigns.
2701
+ *
2702
+ * The lookup runs asynchronously and takes 1–5 minutes depending on list
2703
+ * size. Results populate the `lineType`, `carrierName`, and `invalidReason`
2704
+ * fields on affected contacts — fetch the contact again after a few minutes
2705
+ * to see updated state.
2706
+ *
2707
+ * Idempotent: re-triggering while a lookup is already running for the same
2708
+ * scope is a no-op.
2709
+ *
2710
+ * @param options - Optional: scope to a single list, or force re-check
2711
+ * @returns Acknowledgement
2712
+ *
2713
+ * @example
2714
+ * ```typescript
2715
+ * // Check all un-checked contacts
2716
+ * await sendly.contacts.checkNumbers();
2717
+ *
2718
+ * // Check a specific list only
2719
+ * await sendly.contacts.checkNumbers({ listId: 'lst_xxx' });
2720
+ *
2721
+ * // Re-check contacts even if previously looked up
2722
+ * await sendly.contacts.checkNumbers({ force: true });
2723
+ * ```
2724
+ */
2725
+ async checkNumbers(options = {}) {
2726
+ return this.http.request({
2727
+ method: "POST",
2728
+ path: "/contacts/lookup",
2729
+ body: {
2730
+ listId: options.listId ?? null,
2731
+ force: options.force ?? false
2732
+ }
2733
+ });
2734
+ }
2553
2735
  transformContact(raw) {
2736
+ const r = raw;
2554
2737
  return {
2555
2738
  id: raw.id,
2556
- phoneNumber: raw.phone_number,
2739
+ phoneNumber: raw.phone_number ?? r.phoneNumber,
2557
2740
  name: raw.name,
2558
2741
  email: raw.email,
2559
2742
  metadata: raw.metadata,
2560
- optedOut: raw.opted_out,
2561
- createdAt: raw.created_at,
2562
- updatedAt: raw.updated_at,
2743
+ optedOut: raw.opted_out ?? r.optedOut,
2744
+ lineType: raw.line_type ?? r.lineType ?? null,
2745
+ carrierName: raw.carrier_name ?? r.carrierName ?? null,
2746
+ lineTypeCheckedAt: raw.line_type_checked_at ?? r.lineTypeCheckedAt ?? null,
2747
+ invalidReason: raw.invalid_reason ?? r.invalidReason ?? null,
2748
+ invalidatedAt: raw.invalidated_at ?? r.invalidatedAt ?? null,
2749
+ userMarkedValidAt: raw.user_marked_valid_at ?? r.userMarkedValidAt ?? null,
2750
+ createdAt: raw.created_at ?? r.createdAt,
2751
+ updatedAt: raw.updated_at ?? r.updatedAt,
2563
2752
  lists: raw.lists?.map((l) => ({ id: l.id, name: l.name }))
2564
2753
  };
2565
2754
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendly/node",
3
- "version": "3.27.2",
3
+ "version": "3.30.0",
4
4
  "description": "Official Sendly Node.js SDK for SMS messaging",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",