@textbee/sdk 0.0.3 → 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,17 +71,33 @@ 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', // filter is lowercase: '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
 
83
- // Each message reports its direction uppercase, so compare accordingly
84
- data.filter((m) => m.type === 'RECEIVED')
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
+ }
85
101
 
86
102
  // A single message and its current status
87
103
  const sms = await textbee.getSms(deviceId, smsId)
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.3"}`;
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,14 +108,29 @@ 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
121
  /**
89
- * Direction, uppercase. Note the asymmetry with the `type` filter accepted by
90
- * getMessages, which is lowercase.
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.
91
126
  */
92
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;
93
134
  /** Lowercase, and absent on messages stored before status tracking. */
94
135
  status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
95
136
  sender?: string;
@@ -120,15 +161,23 @@ interface SmsBatch {
120
161
  createdAt?: string;
121
162
  updatedAt?: string;
122
163
  }
123
- interface PaginationMeta {
124
- 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 {
125
170
  limit: number;
126
- total: number;
127
- 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;
128
177
  }
129
- interface MessagesPage {
178
+ interface MessageList {
130
179
  data: Message[];
131
- meta: PaginationMeta;
180
+ meta: MessageListMeta;
132
181
  }
133
182
  interface SmsBatchResult {
134
183
  batch: SmsBatch;
@@ -148,8 +197,24 @@ declare class Textbee {
148
197
  getDevice(deviceId: string): Promise<Device>;
149
198
  /** Make a device the default sender for requests that omit `deviceId`. */
150
199
  setDefaultDevice(deviceId: string): Promise<Device>;
151
- /** Page through a device's sent and received messages. */
152
- 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>;
153
218
  /** Fetch a single message, including its delivery status. */
154
219
  getSms(deviceId: string, smsId: string): Promise<Message>;
155
220
  /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
@@ -191,4 +256,4 @@ interface VerifyWebhookSignatureOptions {
191
256
  */
192
257
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
193
258
 
194
- 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,14 +108,29 @@ 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
121
  /**
89
- * Direction, uppercase. Note the asymmetry with the `type` filter accepted by
90
- * getMessages, which is lowercase.
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.
91
126
  */
92
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;
93
134
  /** Lowercase, and absent on messages stored before status tracking. */
94
135
  status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
95
136
  sender?: string;
@@ -120,15 +161,23 @@ interface SmsBatch {
120
161
  createdAt?: string;
121
162
  updatedAt?: string;
122
163
  }
123
- interface PaginationMeta {
124
- 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 {
125
170
  limit: number;
126
- total: number;
127
- 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;
128
177
  }
129
- interface MessagesPage {
178
+ interface MessageList {
130
179
  data: Message[];
131
- meta: PaginationMeta;
180
+ meta: MessageListMeta;
132
181
  }
133
182
  interface SmsBatchResult {
134
183
  batch: SmsBatch;
@@ -148,8 +197,24 @@ declare class Textbee {
148
197
  getDevice(deviceId: string): Promise<Device>;
149
198
  /** Make a device the default sender for requests that omit `deviceId`. */
150
199
  setDefaultDevice(deviceId: string): Promise<Device>;
151
- /** Page through a device's sent and received messages. */
152
- 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>;
153
218
  /** Fetch a single message, including its delivery status. */
154
219
  getSms(deviceId: string, smsId: string): Promise<Message>;
155
220
  /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
@@ -191,4 +256,4 @@ interface VerifyWebhookSignatureOptions {
191
256
  */
192
257
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
193
258
 
194
- 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.3"}`;
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.3",
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",