From 0750b532aa1e11cbf8652dc1530afee9d3407047 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 15:47:20 +0300 Subject: [PATCH 01/10] feat: add errorHistory to the Message type Co-Authored-By: Claude Opus 5.5 --- src/index.ts | 2 ++ src/types.ts | 19 +++++++++++++++++++ test/client.test.ts | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/index.ts b/src/index.ts index ee70575..214104a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,8 @@ export type { ListMessagesOptions, Message, MessageDevice, + MessageErrorEntry, + MessageErrorSource, MessageList, MessageListMeta, SendSmsImmediateResponse, diff --git a/src/types.ts b/src/types.ts index c3b560d..645d43b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,6 +168,20 @@ export interface MessageDevice { enabled?: boolean } +/** Where a failure was reported from. */ +export type MessageErrorSource = 'push' | 'device' | 'server' + +/** One failure a message had. Kept after the message later succeeds. */ +export interface MessageErrorEntry { + code: string + message?: string + /** When the failure happened. ISO date. */ + at: string + source: MessageErrorSource + /** Set on entries rebuilt from old message fields. */ + backfilled?: boolean +} + export interface Message { _id: string message: string @@ -199,10 +213,15 @@ export interface Message { dispatchedAt?: string sentAt?: string deliveredAt?: string + /** When the message failed. Present only while status is `failed`. */ failedAt?: string receivedAt?: string + /** Current failure code. Present only while status is `failed`. */ errorCode?: string + /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string + /** Every failure the message had, oldest first, at most 5. */ + errorHistory?: MessageErrorEntry[] simSubscriptionId?: number smsBatch?: string createdAt?: string diff --git a/test/client.test.ts b/test/client.test.ts index 78aa086..6776dd6 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Textbee, TextbeeError } from '../src/index' +import type { MessageErrorEntry } from '../src/index' const pkg = JSON.parse( readFileSync(new URL('../package.json', import.meta.url), 'utf8'), @@ -252,6 +253,26 @@ describe('response envelopes', () => { expect(url.searchParams.get('type')).toBeNull() }) + it('types errorHistory on a message that failed and later succeeded', async () => { + const entry: MessageErrorEntry = { + code: 'FCM_SEND_FAILED', + message: 'push failed', + at: '2026-10-01T10:00:00.000Z', + source: 'push', + backfilled: true, + } + const page = { + data: [{ _id: 'sms-2', message: 'hi', type: 'SENT', status: 'sent', errorHistory: [entry] }], + meta: {}, + } + fetchMock.mockImplementation(async () => respond(200, page)) + + const result = await client().getMessages() + + expect(result.data[0].errorHistory).toEqual([entry]) + expect(result.data[0].errorCode).toBeUndefined() + }) + it('sends no query at all for a bare account-level getMessages()', async () => { fetchMock.mockImplementation(async () => respond(200, { data: [], meta: {} })) From a064fc9f787e3d12faff08f7cc644bcd13493a99 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 15:51:26 +0300 Subject: [PATCH 02/10] docs: errorHistory keeps up to 10 entries Co-Authored-By: Claude Opus 5.5 --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 645d43b..6ec6b8c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -220,7 +220,7 @@ export interface Message { errorCode?: string /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string - /** Every failure the message had, oldest first, at most 5. */ + /** Every failure the message had, oldest first, at most 10. */ errorHistory?: MessageErrorEntry[] simSubscriptionId?: number smsBatch?: string From 47b453f4748eff7f6c91c3a9ff225065bfa376bb Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 16:07:03 +0300 Subject: [PATCH 03/10] test: avoid an unchecked index in the errorHistory test Co-Authored-By: Claude Opus 5.5 --- test/client.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/client.test.ts b/test/client.test.ts index 6776dd6..dc998c3 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -269,8 +269,8 @@ describe('response envelopes', () => { const result = await client().getMessages() - expect(result.data[0].errorHistory).toEqual([entry]) - expect(result.data[0].errorCode).toBeUndefined() + expect(result.data[0]?.errorHistory).toEqual([entry]) + expect(result.data[0]?.errorCode).toBeUndefined() }) it('sends no query at all for a bare account-level getMessages()', async () => { From 87795f025dfd510fa0b0594cb84b8b679e1f6f98 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 16:15:51 +0300 Subject: [PATCH 04/10] refactor: drop vacuous assert, note FCM_SEND_SKIPPED in errorCode doc Co-Authored-By: Claude Opus 5.5 --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 6ec6b8c..5a65f42 100644 --- a/src/types.ts +++ b/src/types.ts @@ -216,7 +216,7 @@ export interface Message { /** When the message failed. Present only while status is `failed`. */ failedAt?: string receivedAt?: string - /** Current failure code. Present only while status is `failed`. */ + /** Current failure code. Present only while status is `failed` (test accounts can keep `FCM_SEND_SKIPPED`). */ errorCode?: string /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string From 2a57819c45cf891c7d4375747a53faa554ea3339 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 16:24:39 +0300 Subject: [PATCH 05/10] test: drop the errorCode assert that checked nothing Co-Authored-By: Claude Opus 5.5 --- test/client.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/client.test.ts b/test/client.test.ts index dc998c3..08e2091 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -270,7 +270,6 @@ describe('response envelopes', () => { const result = await client().getMessages() expect(result.data[0]?.errorHistory).toEqual([entry]) - expect(result.data[0]?.errorCode).toBeUndefined() }) it('sends no query at all for a bare account-level getMessages()', async () => { From 4169427d05dec0d52bd6bd9ae6b6419416be7fd9 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 16:32:55 +0300 Subject: [PATCH 06/10] docs: errorHistory keeps the first 5 and the newest 10 Co-Authored-By: Claude Opus 5.5 --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 5a65f42..a4e4979 100644 --- a/src/types.ts +++ b/src/types.ts @@ -220,7 +220,7 @@ export interface Message { errorCode?: string /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string - /** Every failure the message had, oldest first, at most 10. */ + /** Every failure the message had, Oldest first. At most 15: the first 5 and the newest 10. */ errorHistory?: MessageErrorEntry[] simSubscriptionId?: number smsBatch?: string From 2fb90d6d73412f6ed7a4bc93298abfa5f8322eac Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 16:48:23 +0300 Subject: [PATCH 07/10] feat: add deliveryReport to MessageErrorEntry Co-Authored-By: Claude Opus 5.5 --- src/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types.ts b/src/types.ts index a4e4979..61a6080 100644 --- a/src/types.ts +++ b/src/types.ts @@ -180,6 +180,8 @@ export interface MessageErrorEntry { source: MessageErrorSource /** Set on entries rebuilt from old message fields. */ backfilled?: boolean + /** True when the entry is a delivery report that did not confirm delivery. The message was sent; this is not a send failure. */ + deliveryReport?: boolean } export interface Message { From 3aabc22eafd18e769473f77cc141a0d20b7d2943 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 17:53:47 +0300 Subject: [PATCH 08/10] refactor: rename errorHistory to issueHistory Co-Authored-By: Claude Opus 5.5 --- src/index.ts | 4 ++-- src/types.ts | 8 ++++---- test/client.test.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 214104a..7c52bf5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,8 +23,8 @@ export type { ListMessagesOptions, Message, MessageDevice, - MessageErrorEntry, - MessageErrorSource, + MessageIssueEntry, + MessageIssueSource, MessageList, MessageListMeta, SendSmsImmediateResponse, diff --git a/src/types.ts b/src/types.ts index 61a6080..cb795f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -169,15 +169,15 @@ export interface MessageDevice { } /** Where a failure was reported from. */ -export type MessageErrorSource = 'push' | 'device' | 'server' +export type MessageIssueSource = 'push' | 'device' | 'server' /** One failure a message had. Kept after the message later succeeds. */ -export interface MessageErrorEntry { +export interface MessageIssueEntry { code: string message?: string /** When the failure happened. ISO date. */ at: string - source: MessageErrorSource + source: MessageIssueSource /** Set on entries rebuilt from old message fields. */ backfilled?: boolean /** True when the entry is a delivery report that did not confirm delivery. The message was sent; this is not a send failure. */ @@ -223,7 +223,7 @@ export interface Message { /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string /** Every failure the message had, Oldest first. At most 15: the first 5 and the newest 10. */ - errorHistory?: MessageErrorEntry[] + issueHistory?: MessageIssueEntry[] simSubscriptionId?: number smsBatch?: string createdAt?: string diff --git a/test/client.test.ts b/test/client.test.ts index 08e2091..cc2f108 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'node:fs' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Textbee, TextbeeError } from '../src/index' -import type { MessageErrorEntry } from '../src/index' +import type { MessageIssueEntry } from '../src/index' const pkg = JSON.parse( readFileSync(new URL('../package.json', import.meta.url), 'utf8'), @@ -253,8 +253,8 @@ describe('response envelopes', () => { expect(url.searchParams.get('type')).toBeNull() }) - it('types errorHistory on a message that failed and later succeeded', async () => { - const entry: MessageErrorEntry = { + it('types issueHistory on a message that failed and later succeeded', async () => { + const entry: MessageIssueEntry = { code: 'FCM_SEND_FAILED', message: 'push failed', at: '2026-10-01T10:00:00.000Z', @@ -262,14 +262,14 @@ describe('response envelopes', () => { backfilled: true, } const page = { - data: [{ _id: 'sms-2', message: 'hi', type: 'SENT', status: 'sent', errorHistory: [entry] }], + data: [{ _id: 'sms-2', message: 'hi', type: 'SENT', status: 'sent', issueHistory: [entry] }], meta: {}, } fetchMock.mockImplementation(async () => respond(200, page)) const result = await client().getMessages() - expect(result.data[0]?.errorHistory).toEqual([entry]) + expect(result.data[0]?.issueHistory).toEqual([entry]) }) it('sends no query at all for a bare account-level getMessages()', async () => { From 1aab48efbfe9203e89df0e4d4b39ba2f5b5ca083 Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 17:53:51 +0300 Subject: [PATCH 09/10] docs: issueHistory also holds unconfirmed delivery reports Co-Authored-By: Claude Opus 5.5 --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index cb795f7..dc73b6e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -222,7 +222,7 @@ export interface Message { errorCode?: string /** Current failure text. Present only while status is `failed`, or as a timeout note on `unknown`. */ errorMessage?: string - /** Every failure the message had, Oldest first. At most 15: the first 5 and the newest 10. */ + /** Every failure the message had, plus delivery reports that did not confirm delivery (`deliveryReport: true`, not failures). Oldest first. At most 15: the first 5 and the newest 10. */ issueHistory?: MessageIssueEntry[] simSubscriptionId?: number smsBatch?: string From f74c7e98d0a9047c97aff298a926afe95d7ea1ae Mon Sep 17 00:00:00 2001 From: isra el Date: Wed, 28 Oct 2026 18:17:33 +0300 Subject: [PATCH 10/10] chore: release 0.4.0 Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2d71ff..1291c64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@textbee/sdk", - "version": "0.3.1", + "version": "0.4.0", "description": "Official JavaScript SDK for textbee.dev, the open source SMS gateway", "license": "MIT", "packageManager": "pnpm@9.14.2",