@textbee/sdk 0.0.2 → 0.1.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/README.md CHANGED
@@ -71,15 +71,34 @@ await textbee.setDefaultDevice(deviceId)
71
71
 
72
72
  ## Messages and delivery status
73
73
 
74
+ History is account-level: one call covers every device, and `deviceIds` narrows it.
75
+
74
76
  ```js
75
- // Paginated history, filterable and searchable
76
- const { data, meta } = await textbee.getMessages(deviceId, {
77
- type: 'received', // 'all' | 'sent' | 'received'
77
+ // Paginated history across the whole account, filterable and searchable
78
+ const { data, meta } = await textbee.getMessages({
79
+ direction: 'received', // 'all' | 'sent' | 'received'
80
+ deviceIds: [deviceId], // omit for every device
81
+ status: 'delivered', // delivery state; direction=sent + status=failed lists failed sends
82
+ search: 'invoice',
83
+ from: '2026-08-01', // dates are UTC; datetimes need an explicit timezone
84
+ to: '2026-09-01T00:00:00Z', // exclusive, so windows never double-count
78
85
  page: 1,
79
86
  limit: 50,
80
- search: 'invoice',
81
87
  })
82
88
 
89
+ // direction on each message is lowercase and feeds straight back into filters
90
+ data.filter((m) => m.direction === 'received')
91
+
92
+ // Which recipients of a bulk send failed: filter by the batch a send returned
93
+ const { smsBatchId } = await textbee.sendSms({ recipients, message })
94
+ const failed = await textbee.getMessages({ smsBatchId, status: 'failed' })
95
+
96
+ // Drain everything matching a filter: iterateMessages follows the
97
+ // pagination cursor for you until there is nothing left
98
+ for await (const message of textbee.iterateMessages({ direction: 'received', order: 'asc' })) {
99
+ console.log(message.sender, message.message)
100
+ }
101
+
83
102
  // A single message and its current status
84
103
  const sms = await textbee.getSms(deviceId, smsId)
85
104
 
@@ -137,7 +156,7 @@ new Textbee({
137
156
 
138
157
  ## What is not covered yet
139
158
 
140
- v0.0.1 focuses on sending and reading messages. Bulk send and a few device operations are still REST only, documented at [textbee.dev/docs](https://textbee.dev/docs).
159
+ The SDK focuses on sending and reading messages. Bulk send and a few device operations are still REST only, documented at [textbee.dev/docs](https://textbee.dev/docs).
141
160
 
142
161
  ## License
143
162
 
package/dist/index.cjs CHANGED
@@ -42,7 +42,7 @@ var TextbeeError = class extends Error {
42
42
 
43
43
  // src/client.ts
44
44
  var DEFAULT_BASE_URL = "https://api.textbee.dev/api/v1";
45
- var CLIENT_ID = `textbee-js/${"0.0.2"}`;
45
+ var CLIENT_ID = `textbee-js/${"0.1.0"}`;
46
46
  var Textbee = class {
47
47
  #apiKey;
48
48
  #baseUrl;
@@ -99,20 +99,46 @@ var Textbee = class {
99
99
  );
100
100
  return payload.data;
101
101
  }
102
- /** Page through a device's sent and received messages. */
103
- async getMessages(deviceId, options = {}) {
104
- return await this.#request(
105
- "GET",
106
- `/gateway/devices/${encodeURIComponent(deviceId)}/messages`,
107
- {
108
- query: {
109
- type: options.type,
110
- page: options.page,
111
- limit: options.limit,
112
- search: options.search
113
- }
102
+ /**
103
+ * Page through the account's sent and received messages, filtered by
104
+ * device, batch, direction, status, text, and time range.
105
+ */
106
+ async getMessages(options = {}) {
107
+ return await this.#request("GET", "/gateway/messages", {
108
+ query: {
109
+ deviceIds: options.deviceIds?.length ? options.deviceIds.join(",") : void 0,
110
+ smsBatchId: options.smsBatchId,
111
+ direction: options.direction,
112
+ status: options.status,
113
+ search: options.search,
114
+ from: toIsoString(options.from),
115
+ to: toIsoString(options.to),
116
+ order: options.order,
117
+ page: options.page,
118
+ limit: options.limit,
119
+ cursor: options.cursor
114
120
  }
115
- );
121
+ });
122
+ }
123
+ /**
124
+ * Iterate every message matching the filters, following the pagination
125
+ * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
126
+ * forward when polling; keep the last message's `createdAt` (or track
127
+ * `meta.nextCursor` via `getMessages`) to resume the next poll.
128
+ *
129
+ * ```ts
130
+ * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
131
+ * handle(message)
132
+ * }
133
+ * ```
134
+ */
135
+ async *iterateMessages(options = {}) {
136
+ let page = await this.getMessages(options);
137
+ yield* page.data;
138
+ while (page.meta.nextCursor) {
139
+ page = await this.getMessages({ ...options, cursor: page.meta.nextCursor });
140
+ yield* page.data;
141
+ }
116
142
  }
117
143
  /** Fetch a single message, including its delivery status. */
118
144
  async getSms(deviceId, smsId) {
@@ -158,6 +184,9 @@ var Textbee = class {
158
184
  return payload;
159
185
  }
160
186
  };
187
+ function toIsoString(value) {
188
+ return value instanceof Date ? value.toISOString() : value;
189
+ }
161
190
  async function readBody(response) {
162
191
  const text = await response.text();
163
192
  if (!text) {
package/dist/index.d.cts CHANGED
@@ -54,16 +54,42 @@ interface SendSmsImmediateResponse {
54
54
  * with `'smsBatchId' in result` before reading batch fields.
55
55
  */
56
56
  type SendSmsResponse = SendSmsQueuedResponse | SendSmsImmediateResponse;
57
- interface GetMessagesOptions {
57
+ interface ListMessagesOptions {
58
+ /** Only messages from these devices. Omit for every device on the account. */
59
+ deviceIds?: string[];
60
+ /**
61
+ * Only messages from this batch, using the `smsBatchId` a send returns.
62
+ * Combine with `status: 'failed'` to list the recipients that failed.
63
+ */
64
+ smsBatchId?: string;
58
65
  /** Filter by direction. Defaults to `all`. */
59
- type?: 'all' | 'sent' | 'received';
60
- /** 1-based page number. Defaults to 1. */
66
+ direction?: 'all' | 'sent' | 'received';
67
+ /** Filter by delivery state, e.g. `failed` to list sends that failed. */
68
+ status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
69
+ /** Free text match across the message body, recipient, and sender. */
70
+ search?: string;
71
+ /**
72
+ * Inclusive lower bound on when the platform stored the message. A string
73
+ * must carry an explicit timezone (`2026-08-01T00:00:00Z`); a bare date like
74
+ * `2026-08-01` is read as UTC midnight.
75
+ */
76
+ from?: string | Date;
77
+ /** Exclusive upper bound, same formats as `from`. */
78
+ to?: string | Date;
79
+ /** `desc` (default) for newest first, `asc` to walk forward when polling. */
80
+ order?: 'desc' | 'asc';
81
+ /** 1-based page number. Defaults to 1. Mutually exclusive with `cursor`. */
61
82
  page?: number;
62
83
  /** Items per page. Defaults to 50, capped at 100 by the API. */
63
84
  limit?: number;
64
- /** Free text match across the message body, recipient, and sender. */
65
- search?: string;
85
+ /**
86
+ * Opaque position from a previous response's `meta.nextCursor`. When set,
87
+ * `meta` switches to cursor mode and omits the total count.
88
+ */
89
+ cursor?: string;
66
90
  }
91
+ /** Everything `iterateMessages` accepts: a filter set, minus the paging knobs it drives itself. */
92
+ type IterateMessagesOptions = Omit<ListMessagesOptions, 'page' | 'cursor'>;
67
93
  interface Device {
68
94
  _id: string;
69
95
  name?: string;
@@ -82,11 +108,31 @@ interface Device {
82
108
  createdAt?: string;
83
109
  updatedAt?: string;
84
110
  }
111
+ interface MessageDevice {
112
+ _id: string;
113
+ brand?: string;
114
+ model?: string;
115
+ buildId?: string;
116
+ enabled?: boolean;
117
+ }
85
118
  interface Message {
86
119
  _id: string;
87
120
  message: string;
88
- type: 'sent' | 'received';
89
- status: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
121
+ /**
122
+ * Direction, uppercase.
123
+ *
124
+ * @deprecated Read `direction` instead: it is lowercase and matches the
125
+ * `direction` filter, so a response value feeds straight back into a query.
126
+ */
127
+ type: 'SENT' | 'RECEIVED';
128
+ /** Direction, lowercase. Matches the `direction` filter values. */
129
+ direction?: 'sent' | 'received';
130
+ /** Message channel. Currently always `sms`; absent means `sms`. */
131
+ channel?: string;
132
+ /** The sending or receiving device, populated by the history endpoints. */
133
+ device?: MessageDevice;
134
+ /** Lowercase, and absent on messages stored before status tracking. */
135
+ status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
90
136
  sender?: string;
91
137
  recipient?: string;
92
138
  requestedAt?: string;
@@ -115,15 +161,23 @@ interface SmsBatch {
115
161
  createdAt?: string;
116
162
  updatedAt?: string;
117
163
  }
118
- interface PaginationMeta {
119
- page: number;
164
+ /**
165
+ * Meta for the account-level message list. Page mode carries the counters and
166
+ * the cursor fields; cursor mode carries `limit`, `nextCursor`, and `hasMore`
167
+ * only, skipping the expensive total count.
168
+ */
169
+ interface MessageListMeta {
120
170
  limit: number;
121
- total: number;
122
- totalPages: number;
171
+ page?: number;
172
+ total?: number;
173
+ totalPages?: number;
174
+ /** Opaque position after the last message. Null on the final page. */
175
+ nextCursor?: string | null;
176
+ hasMore?: boolean;
123
177
  }
124
- interface MessagesPage {
178
+ interface MessageList {
125
179
  data: Message[];
126
- meta: PaginationMeta;
180
+ meta: MessageListMeta;
127
181
  }
128
182
  interface SmsBatchResult {
129
183
  batch: SmsBatch;
@@ -143,8 +197,24 @@ declare class Textbee {
143
197
  getDevice(deviceId: string): Promise<Device>;
144
198
  /** Make a device the default sender for requests that omit `deviceId`. */
145
199
  setDefaultDevice(deviceId: string): Promise<Device>;
146
- /** Page through a device's sent and received messages. */
147
- getMessages(deviceId: string, options?: GetMessagesOptions): Promise<MessagesPage>;
200
+ /**
201
+ * Page through the account's sent and received messages, filtered by
202
+ * device, batch, direction, status, text, and time range.
203
+ */
204
+ getMessages(options?: ListMessagesOptions): Promise<MessageList>;
205
+ /**
206
+ * Iterate every message matching the filters, following the pagination
207
+ * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
208
+ * forward when polling; keep the last message's `createdAt` (or track
209
+ * `meta.nextCursor` via `getMessages`) to resume the next poll.
210
+ *
211
+ * ```ts
212
+ * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
213
+ * handle(message)
214
+ * }
215
+ * ```
216
+ */
217
+ iterateMessages(options?: IterateMessagesOptions): AsyncGenerator<Message, void, undefined>;
148
218
  /** Fetch a single message, including its delivery status. */
149
219
  getSms(deviceId: string, smsId: string): Promise<Message>;
150
220
  /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
@@ -186,4 +256,4 @@ interface VerifyWebhookSignatureOptions {
186
256
  */
187
257
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
188
258
 
189
- export { type Device, type GetMessagesOptions, type Message, type MessagesPage, type PaginationMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
259
+ export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -54,16 +54,42 @@ interface SendSmsImmediateResponse {
54
54
  * with `'smsBatchId' in result` before reading batch fields.
55
55
  */
56
56
  type SendSmsResponse = SendSmsQueuedResponse | SendSmsImmediateResponse;
57
- interface GetMessagesOptions {
57
+ interface ListMessagesOptions {
58
+ /** Only messages from these devices. Omit for every device on the account. */
59
+ deviceIds?: string[];
60
+ /**
61
+ * Only messages from this batch, using the `smsBatchId` a send returns.
62
+ * Combine with `status: 'failed'` to list the recipients that failed.
63
+ */
64
+ smsBatchId?: string;
58
65
  /** Filter by direction. Defaults to `all`. */
59
- type?: 'all' | 'sent' | 'received';
60
- /** 1-based page number. Defaults to 1. */
66
+ direction?: 'all' | 'sent' | 'received';
67
+ /** Filter by delivery state, e.g. `failed` to list sends that failed. */
68
+ status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
69
+ /** Free text match across the message body, recipient, and sender. */
70
+ search?: string;
71
+ /**
72
+ * Inclusive lower bound on when the platform stored the message. A string
73
+ * must carry an explicit timezone (`2026-08-01T00:00:00Z`); a bare date like
74
+ * `2026-08-01` is read as UTC midnight.
75
+ */
76
+ from?: string | Date;
77
+ /** Exclusive upper bound, same formats as `from`. */
78
+ to?: string | Date;
79
+ /** `desc` (default) for newest first, `asc` to walk forward when polling. */
80
+ order?: 'desc' | 'asc';
81
+ /** 1-based page number. Defaults to 1. Mutually exclusive with `cursor`. */
61
82
  page?: number;
62
83
  /** Items per page. Defaults to 50, capped at 100 by the API. */
63
84
  limit?: number;
64
- /** Free text match across the message body, recipient, and sender. */
65
- search?: string;
85
+ /**
86
+ * Opaque position from a previous response's `meta.nextCursor`. When set,
87
+ * `meta` switches to cursor mode and omits the total count.
88
+ */
89
+ cursor?: string;
66
90
  }
91
+ /** Everything `iterateMessages` accepts: a filter set, minus the paging knobs it drives itself. */
92
+ type IterateMessagesOptions = Omit<ListMessagesOptions, 'page' | 'cursor'>;
67
93
  interface Device {
68
94
  _id: string;
69
95
  name?: string;
@@ -82,11 +108,31 @@ interface Device {
82
108
  createdAt?: string;
83
109
  updatedAt?: string;
84
110
  }
111
+ interface MessageDevice {
112
+ _id: string;
113
+ brand?: string;
114
+ model?: string;
115
+ buildId?: string;
116
+ enabled?: boolean;
117
+ }
85
118
  interface Message {
86
119
  _id: string;
87
120
  message: string;
88
- type: 'sent' | 'received';
89
- status: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
121
+ /**
122
+ * Direction, uppercase.
123
+ *
124
+ * @deprecated Read `direction` instead: it is lowercase and matches the
125
+ * `direction` filter, so a response value feeds straight back into a query.
126
+ */
127
+ type: 'SENT' | 'RECEIVED';
128
+ /** Direction, lowercase. Matches the `direction` filter values. */
129
+ direction?: 'sent' | 'received';
130
+ /** Message channel. Currently always `sms`; absent means `sms`. */
131
+ channel?: string;
132
+ /** The sending or receiving device, populated by the history endpoints. */
133
+ device?: MessageDevice;
134
+ /** Lowercase, and absent on messages stored before status tracking. */
135
+ status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
90
136
  sender?: string;
91
137
  recipient?: string;
92
138
  requestedAt?: string;
@@ -115,15 +161,23 @@ interface SmsBatch {
115
161
  createdAt?: string;
116
162
  updatedAt?: string;
117
163
  }
118
- interface PaginationMeta {
119
- page: number;
164
+ /**
165
+ * Meta for the account-level message list. Page mode carries the counters and
166
+ * the cursor fields; cursor mode carries `limit`, `nextCursor`, and `hasMore`
167
+ * only, skipping the expensive total count.
168
+ */
169
+ interface MessageListMeta {
120
170
  limit: number;
121
- total: number;
122
- totalPages: number;
171
+ page?: number;
172
+ total?: number;
173
+ totalPages?: number;
174
+ /** Opaque position after the last message. Null on the final page. */
175
+ nextCursor?: string | null;
176
+ hasMore?: boolean;
123
177
  }
124
- interface MessagesPage {
178
+ interface MessageList {
125
179
  data: Message[];
126
- meta: PaginationMeta;
180
+ meta: MessageListMeta;
127
181
  }
128
182
  interface SmsBatchResult {
129
183
  batch: SmsBatch;
@@ -143,8 +197,24 @@ declare class Textbee {
143
197
  getDevice(deviceId: string): Promise<Device>;
144
198
  /** Make a device the default sender for requests that omit `deviceId`. */
145
199
  setDefaultDevice(deviceId: string): Promise<Device>;
146
- /** Page through a device's sent and received messages. */
147
- getMessages(deviceId: string, options?: GetMessagesOptions): Promise<MessagesPage>;
200
+ /**
201
+ * Page through the account's sent and received messages, filtered by
202
+ * device, batch, direction, status, text, and time range.
203
+ */
204
+ getMessages(options?: ListMessagesOptions): Promise<MessageList>;
205
+ /**
206
+ * Iterate every message matching the filters, following the pagination
207
+ * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
208
+ * forward when polling; keep the last message's `createdAt` (or track
209
+ * `meta.nextCursor` via `getMessages`) to resume the next poll.
210
+ *
211
+ * ```ts
212
+ * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
213
+ * handle(message)
214
+ * }
215
+ * ```
216
+ */
217
+ iterateMessages(options?: IterateMessagesOptions): AsyncGenerator<Message, void, undefined>;
148
218
  /** Fetch a single message, including its delivery status. */
149
219
  getSms(deviceId: string, smsId: string): Promise<Message>;
150
220
  /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
@@ -186,4 +256,4 @@ interface VerifyWebhookSignatureOptions {
186
256
  */
187
257
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
188
258
 
189
- export { type Device, type GetMessagesOptions, type Message, type MessagesPage, type PaginationMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
259
+ export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var TextbeeError = class extends Error {
14
14
 
15
15
  // src/client.ts
16
16
  var DEFAULT_BASE_URL = "https://api.textbee.dev/api/v1";
17
- var CLIENT_ID = `textbee-js/${"0.0.2"}`;
17
+ var CLIENT_ID = `textbee-js/${"0.1.0"}`;
18
18
  var Textbee = class {
19
19
  #apiKey;
20
20
  #baseUrl;
@@ -71,20 +71,46 @@ var Textbee = class {
71
71
  );
72
72
  return payload.data;
73
73
  }
74
- /** Page through a device's sent and received messages. */
75
- async getMessages(deviceId, options = {}) {
76
- return await this.#request(
77
- "GET",
78
- `/gateway/devices/${encodeURIComponent(deviceId)}/messages`,
79
- {
80
- query: {
81
- type: options.type,
82
- page: options.page,
83
- limit: options.limit,
84
- search: options.search
85
- }
74
+ /**
75
+ * Page through the account's sent and received messages, filtered by
76
+ * device, batch, direction, status, text, and time range.
77
+ */
78
+ async getMessages(options = {}) {
79
+ return await this.#request("GET", "/gateway/messages", {
80
+ query: {
81
+ deviceIds: options.deviceIds?.length ? options.deviceIds.join(",") : void 0,
82
+ smsBatchId: options.smsBatchId,
83
+ direction: options.direction,
84
+ status: options.status,
85
+ search: options.search,
86
+ from: toIsoString(options.from),
87
+ to: toIsoString(options.to),
88
+ order: options.order,
89
+ page: options.page,
90
+ limit: options.limit,
91
+ cursor: options.cursor
86
92
  }
87
- );
93
+ });
94
+ }
95
+ /**
96
+ * Iterate every message matching the filters, following the pagination
97
+ * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
98
+ * forward when polling; keep the last message's `createdAt` (or track
99
+ * `meta.nextCursor` via `getMessages`) to resume the next poll.
100
+ *
101
+ * ```ts
102
+ * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
103
+ * handle(message)
104
+ * }
105
+ * ```
106
+ */
107
+ async *iterateMessages(options = {}) {
108
+ let page = await this.getMessages(options);
109
+ yield* page.data;
110
+ while (page.meta.nextCursor) {
111
+ page = await this.getMessages({ ...options, cursor: page.meta.nextCursor });
112
+ yield* page.data;
113
+ }
88
114
  }
89
115
  /** Fetch a single message, including its delivery status. */
90
116
  async getSms(deviceId, smsId) {
@@ -130,6 +156,9 @@ var Textbee = class {
130
156
  return payload;
131
157
  }
132
158
  };
159
+ function toIsoString(value) {
160
+ return value instanceof Date ? value.toISOString() : value;
161
+ }
133
162
  async function readBody(response) {
134
163
  const text = await response.text();
135
164
  if (!text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbee/sdk",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "Official JavaScript SDK for textbee.dev, the open source SMS gateway",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@9.14.2",