@kb-labs/inbox-core 2.96.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 ADDED
@@ -0,0 +1,5 @@
1
+ # @kb-labs/inbox-core
2
+
3
+ > Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem.
4
+
5
+ Business logic for the inbox plugin: IMAP/SMTP connectivity via imapflow and nodemailer. Pure, testable in isolation.
@@ -0,0 +1,84 @@
1
+ import { AccountInfo, Email, Folder, EmailSlim } from '@kb-labs/inbox-contracts';
2
+
3
+ interface AccountConfig {
4
+ name: string;
5
+ user: string;
6
+ pass: string;
7
+ imap: {
8
+ host: string;
9
+ port: number;
10
+ };
11
+ smtp: {
12
+ host: string;
13
+ port: number;
14
+ };
15
+ }
16
+ /**
17
+ * Resolve account config by name or index (default: first account).
18
+ * Throws InboxError with actionable hints if not found.
19
+ */
20
+ declare function resolveAccount(nameOrIndex?: string): AccountConfig;
21
+ /**
22
+ * Returns info for all configured accounts (no passwords).
23
+ */
24
+ declare function listAccounts(): AccountInfo[];
25
+
26
+ type InboxErrorCode = 'ENV_MISSING' | 'ACCOUNT_NOT_FOUND' | 'IMAP_AUTH_FAILED' | 'IMAP_CONNECT_FAILED' | 'IMAP_TIMEOUT' | 'IMAP_MAILBOX_NOT_FOUND' | 'MESSAGE_NOT_FOUND' | 'SMTP_AUTH_FAILED' | 'SMTP_CONNECT_FAILED' | 'SMTP_INVALID_RECIPIENT' | 'VALIDATION_ERROR' | 'INTERNAL_ERROR';
27
+ declare class InboxError extends Error {
28
+ readonly code: InboxErrorCode;
29
+ readonly cause?: unknown | undefined;
30
+ readonly name = "InboxError";
31
+ constructor(code: InboxErrorCode, message: string, cause?: unknown | undefined);
32
+ }
33
+
34
+ interface ListMessagesOptions {
35
+ folder?: string;
36
+ unreadOnly?: boolean;
37
+ since?: Date;
38
+ limit?: number;
39
+ }
40
+ declare function listMessages(config: AccountConfig, opts?: ListMessagesOptions): Promise<EmailSlim[]>;
41
+ interface GetMessageOptions {
42
+ folder?: string;
43
+ withAttachments?: boolean;
44
+ }
45
+ declare function getMessage(config: AccountConfig, uid: number, opts?: GetMessageOptions): Promise<Email>;
46
+ declare function getThread(config: AccountConfig, uid: number, opts?: GetMessageOptions): Promise<Email[]>;
47
+ interface SearchMessagesOptions {
48
+ from?: string;
49
+ subject?: string;
50
+ body?: string;
51
+ text?: string;
52
+ folder?: string;
53
+ since?: Date;
54
+ limit?: number;
55
+ }
56
+ declare function searchMessages(config: AccountConfig, opts: SearchMessagesOptions): Promise<EmailSlim[]>;
57
+ declare function moveMessage(config: AccountConfig, uid: number, fromFolder: string, toFolder: string): Promise<void>;
58
+ type MarkAction = 'read' | 'unread' | 'spam' | 'flagged' | 'unflagged';
59
+ declare function markMessage(config: AccountConfig, uid: number, folder: string, action: MarkAction): Promise<void>;
60
+ declare function deleteMessage(config: AccountConfig, uid: number, folder: string): Promise<void>;
61
+ declare function listFolders(config: AccountConfig): Promise<Folder[]>;
62
+
63
+ interface SendOptions {
64
+ to: string;
65
+ subject: string;
66
+ body: string;
67
+ cc?: string;
68
+ bcc?: string;
69
+ from?: string;
70
+ }
71
+ interface ReplyOptions {
72
+ body: string;
73
+ originalMessageId?: string;
74
+ originalReferences?: string[];
75
+ originalSubject: string;
76
+ }
77
+ declare function sendMessage(config: AccountConfig, opts: SendOptions): Promise<{
78
+ messageId: string;
79
+ }>;
80
+ declare function replyMessage(config: AccountConfig, original: ReplyOptions, body: string): Promise<{
81
+ messageId: string;
82
+ }>;
83
+
84
+ export { type AccountConfig, type GetMessageOptions, InboxError, type InboxErrorCode, type ListMessagesOptions, type MarkAction, type ReplyOptions, type SearchMessagesOptions, type SendOptions, deleteMessage, getMessage, getThread, listAccounts, listFolders, listMessages, markMessage, moveMessage, replyMessage, resolveAccount, searchMessages, sendMessage };
package/dist/index.js ADDED
@@ -0,0 +1,503 @@
1
+ import { useEnv } from '@kb-labs/sdk';
2
+ import { ImapFlow } from 'imapflow';
3
+ import nodemailer from 'nodemailer';
4
+
5
+ // src/env.ts
6
+
7
+ // src/error.ts
8
+ var InboxError = class extends Error {
9
+ constructor(code, message, cause) {
10
+ super(message);
11
+ this.code = code;
12
+ this.cause = cause;
13
+ }
14
+ code;
15
+ cause;
16
+ name = "InboxError";
17
+ };
18
+
19
+ // src/env.ts
20
+ function getAccountNames() {
21
+ const raw = useEnv("INBOX_ACCOUNTS");
22
+ if (!raw) {
23
+ return [];
24
+ }
25
+ return raw.split(",").map((s) => s.trim().toUpperCase()).filter(Boolean);
26
+ }
27
+ function readAccountConfig(name) {
28
+ const prefix = `INBOX_ACCOUNT_${name}`;
29
+ const user = useEnv(`${prefix}_USER`);
30
+ const pass = useEnv(`${prefix}_PASS`);
31
+ const imapHost = useEnv(`${prefix}_IMAP_HOST`);
32
+ const smtpHost = useEnv(`${prefix}_SMTP_HOST`);
33
+ if (!user || !pass || !imapHost || !smtpHost) {
34
+ const missing = [
35
+ !user && `${prefix}_USER`,
36
+ !pass && `${prefix}_PASS`,
37
+ !imapHost && `${prefix}_IMAP_HOST`,
38
+ !smtpHost && `${prefix}_SMTP_HOST`
39
+ ].filter(Boolean).join(", ");
40
+ throw new InboxError(
41
+ "ENV_MISSING",
42
+ `Account "${name.toLowerCase()}" is missing required env vars: ${missing}`
43
+ );
44
+ }
45
+ const imapPortRaw = useEnv(`${prefix}_IMAP_PORT`) ?? "993";
46
+ const smtpPortRaw = useEnv(`${prefix}_SMTP_PORT`) ?? "465";
47
+ return {
48
+ name: name.toLowerCase(),
49
+ user,
50
+ pass,
51
+ imap: { host: imapHost, port: parseInt(imapPortRaw, 10) },
52
+ smtp: { host: smtpHost, port: parseInt(smtpPortRaw, 10) }
53
+ };
54
+ }
55
+ function resolveAccount(nameOrIndex) {
56
+ const names = getAccountNames();
57
+ if (names.length === 0) {
58
+ throw new InboxError(
59
+ "ENV_MISSING",
60
+ "No accounts configured. Set INBOX_ACCOUNTS=work,personal and account vars."
61
+ );
62
+ }
63
+ if (!nameOrIndex) {
64
+ return readAccountConfig(names[0]);
65
+ }
66
+ const byName = names.find((n) => n === nameOrIndex.toUpperCase());
67
+ if (byName) {
68
+ return readAccountConfig(byName);
69
+ }
70
+ const idx = parseInt(nameOrIndex, 10);
71
+ if (!isNaN(idx) && idx >= 0 && idx < names.length) {
72
+ return readAccountConfig(names[idx]);
73
+ }
74
+ throw new InboxError(
75
+ "ACCOUNT_NOT_FOUND",
76
+ `Account "${nameOrIndex}" not found. Available: ${names.map((n) => n.toLowerCase()).join(", ")}`
77
+ );
78
+ }
79
+ function listAccounts() {
80
+ const names = getAccountNames();
81
+ return names.map((name, index) => {
82
+ const prefix = `INBOX_ACCOUNT_${name}`;
83
+ const user = useEnv(`${prefix}_USER`) ?? "(not set)";
84
+ const imapHost = useEnv(`${prefix}_IMAP_HOST`) ?? "(not set)";
85
+ const imapPortRaw = useEnv(`${prefix}_IMAP_PORT`) ?? "993";
86
+ return {
87
+ name: name.toLowerCase(),
88
+ user,
89
+ imapHost,
90
+ imapPort: parseInt(imapPortRaw, 10),
91
+ // index for --account flag
92
+ _index: index
93
+ };
94
+ });
95
+ }
96
+ async function withImap(config, fn) {
97
+ const client = new ImapFlow({
98
+ host: config.imap.host,
99
+ port: config.imap.port,
100
+ secure: true,
101
+ auth: { user: config.user, pass: config.pass },
102
+ logger: false,
103
+ // Fail fast — CLI commands should not hang
104
+ connectionTimeout: 1e4
105
+ });
106
+ try {
107
+ await client.connect();
108
+ } catch (err) {
109
+ throw wrapImapError(err);
110
+ }
111
+ try {
112
+ return await fn(client);
113
+ } catch (err) {
114
+ throw err instanceof InboxError ? err : wrapImapError(err);
115
+ } finally {
116
+ try {
117
+ await client.logout();
118
+ } catch {
119
+ }
120
+ }
121
+ }
122
+ function wrapImapError(err) {
123
+ if (err instanceof InboxError) {
124
+ return err;
125
+ }
126
+ const msg = err instanceof Error ? err.message : String(err);
127
+ const lower = msg.toLowerCase();
128
+ if (/authenticationfailed|invalid credentials|login failed/i.test(lower)) {
129
+ return new InboxError("IMAP_AUTH_FAILED", msg, err);
130
+ }
131
+ if (/econnrefused|enotfound|etimedout|connect/i.test(lower)) {
132
+ return new InboxError("IMAP_CONNECT_FAILED", msg, err);
133
+ }
134
+ if (/timeout/i.test(lower)) {
135
+ return new InboxError("IMAP_TIMEOUT", msg, err);
136
+ }
137
+ if (/no such mailbox|mailbox not found|doesn't exist/i.test(lower)) {
138
+ return new InboxError("IMAP_MAILBOX_NOT_FOUND", msg, err);
139
+ }
140
+ return new InboxError("INTERNAL_ERROR", msg, err);
141
+ }
142
+ function parseAddresses(raw) {
143
+ if (!raw || !Array.isArray(raw)) {
144
+ return [];
145
+ }
146
+ return raw.filter((a) => a.address).map((a) => ({ ...a.name ? { name: a.name } : {}, address: a.address }));
147
+ }
148
+ async function listMessages(config, opts = {}) {
149
+ const folder = opts.folder ?? "INBOX";
150
+ const limit = opts.limit ?? 50;
151
+ return withImap(config, async (client) => {
152
+ await client.mailboxOpen(folder).catch(() => {
153
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found. Use kb inbox folders to list available folders.`);
154
+ });
155
+ const searchCriteria = {};
156
+ if (opts.unreadOnly) {
157
+ searchCriteria["unseen"] = true;
158
+ }
159
+ if (opts.since) {
160
+ searchCriteria["since"] = opts.since;
161
+ }
162
+ const hasFilters = Object.keys(searchCriteria).length > 0;
163
+ const uids = hasFilters ? await client.search(searchCriteria, { uid: true }) : await client.search({ all: true }, { uid: true });
164
+ if (!uids.length) {
165
+ return [];
166
+ }
167
+ const sliced = uids.slice(-limit);
168
+ const results = [];
169
+ for await (const msg of client.fetch(sliced, {
170
+ uid: true,
171
+ flags: true,
172
+ envelope: true,
173
+ bodyStructure: true
174
+ }, { uid: true })) {
175
+ const envelope = msg.envelope;
176
+ if (!envelope) {
177
+ continue;
178
+ }
179
+ const flags = [...msg.flags ?? []];
180
+ const hasAttachments = hasAttachmentParts(msg.bodyStructure);
181
+ results.push({
182
+ uid: msg.uid,
183
+ subject: envelope.subject ?? "(no subject)",
184
+ from: parseAddresses(envelope.from)[0] ?? { address: "" },
185
+ date: envelope.date ? new Date(envelope.date).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString(),
186
+ folder,
187
+ unread: !flags.includes("\\Seen"),
188
+ flagged: flags.includes("\\Flagged"),
189
+ hasAttachments
190
+ });
191
+ }
192
+ return results.sort((a, b) => b.date.localeCompare(a.date));
193
+ });
194
+ }
195
+ function hasAttachmentParts(structure) {
196
+ if (!structure) {
197
+ return false;
198
+ }
199
+ const s = structure;
200
+ if (s.disposition?.type?.toLowerCase() === "attachment") {
201
+ return true;
202
+ }
203
+ if (s.childNodes) {
204
+ return s.childNodes.some(hasAttachmentParts);
205
+ }
206
+ return false;
207
+ }
208
+ async function getMessage(config, uid, opts = {}) {
209
+ const folder = opts.folder ?? "INBOX";
210
+ return withImap(config, async (client) => {
211
+ await client.mailboxOpen(folder).catch(() => {
212
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found.`);
213
+ });
214
+ let found = null;
215
+ for await (const msg of client.fetch([uid], {
216
+ uid: true,
217
+ flags: true,
218
+ envelope: true,
219
+ source: true
220
+ }, { uid: true })) {
221
+ const envelope = msg.envelope;
222
+ if (!envelope) {
223
+ continue;
224
+ }
225
+ const flags = [...msg.flags ?? []];
226
+ const source = msg.source?.toString("utf8") ?? "";
227
+ const { text, html, attachments } = parseBody(source);
228
+ found = {
229
+ uid: msg.uid,
230
+ messageId: envelope.messageId ?? void 0,
231
+ subject: envelope.subject ?? "(no subject)",
232
+ from: parseAddresses(envelope.from)[0] ?? { address: "" },
233
+ to: parseAddresses(envelope.to),
234
+ cc: parseAddresses(envelope.cc),
235
+ date: envelope.date ? new Date(envelope.date).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString(),
236
+ folder,
237
+ flags,
238
+ text,
239
+ html,
240
+ attachments: opts.withAttachments ? attachments : void 0,
241
+ inReplyTo: envelope.inReplyTo ?? void 0,
242
+ references: envelope.references ?? void 0
243
+ };
244
+ }
245
+ if (!found) {
246
+ throw new InboxError("MESSAGE_NOT_FOUND", `Message uid=${uid} not found in "${folder}". Refresh with: kb inbox list --folder ${folder}`);
247
+ }
248
+ return found;
249
+ });
250
+ }
251
+ function parseBody(source) {
252
+ const textMatch = source.match(/Content-Type: text\/plain[^\r\n]*[\r\n]+([\s\S]*?)(?=\r?\n--|\r?\n\r?\n--)/i);
253
+ const htmlMatch = source.match(/Content-Type: text\/html[^\r\n]*[\r\n]+([\s\S]*?)(?=\r?\n--|\r?\n\r?\n--)/i);
254
+ const text = textMatch?.[1] != null ? decodeBodyPart(textMatch[1]) : void 0;
255
+ const html = htmlMatch?.[1] != null ? decodeBodyPart(htmlMatch[1]) : void 0;
256
+ const attachments = [];
257
+ const attachRe = /Content-Disposition: attachment[^\r\n]*(?:\r?\n[^\r\n]+)*\r?\n\s*filename="([^"]+)"/gi;
258
+ const typeRe = /Content-Type: ([^\s;]+)/i;
259
+ let m;
260
+ while ((m = attachRe.exec(source)) !== null) {
261
+ const filename = m[1] ?? "";
262
+ const typeMatch = source.slice(Math.max(0, m.index - 200), m.index).match(typeRe);
263
+ attachments.push({ filename, contentType: typeMatch?.[1] ?? "application/octet-stream", size: 0 });
264
+ }
265
+ return { text, html, attachments };
266
+ }
267
+ function decodeBodyPart(raw) {
268
+ return raw.replace(/^[^\r\n]*[\r\n]+/gm, "").trim();
269
+ }
270
+ async function getThread(config, uid, opts = {}) {
271
+ const folder = opts.folder ?? "INBOX";
272
+ const root = await getMessage(config, uid, { folder });
273
+ if (!root.inReplyTo && !root.messageId) {
274
+ return [root];
275
+ }
276
+ return withImap(config, async (client) => {
277
+ await client.mailboxOpen(folder).catch(() => {
278
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found.`);
279
+ });
280
+ const targetId = root.messageId ?? root.inReplyTo;
281
+ if (!targetId) {
282
+ return [root];
283
+ }
284
+ const uids = await client.search({ header: { "References": targetId } }, { uid: true });
285
+ const selfUids = await client.search({ header: { "Message-ID": targetId } }, { uid: true });
286
+ const allUids = [.../* @__PURE__ */ new Set([...uids, ...selfUids, uid])].sort((a, b) => a - b);
287
+ const messages = [];
288
+ for (const u of allUids) {
289
+ try {
290
+ const msg = await getMessage(config, u, { folder });
291
+ messages.push(msg);
292
+ } catch {
293
+ }
294
+ }
295
+ return messages.sort((a, b) => a.date.localeCompare(b.date));
296
+ });
297
+ }
298
+ async function searchMessages(config, opts) {
299
+ const folder = opts.folder ?? "INBOX";
300
+ const limit = opts.limit ?? 20;
301
+ return withImap(config, async (client) => {
302
+ await client.mailboxOpen(folder).catch(() => {
303
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found.`);
304
+ });
305
+ const criteria = {};
306
+ if (opts.from) {
307
+ criteria["from"] = opts.from;
308
+ }
309
+ if (opts.subject) {
310
+ criteria["subject"] = opts.subject;
311
+ }
312
+ if (opts.body) {
313
+ criteria["body"] = opts.body;
314
+ }
315
+ if (opts.text) {
316
+ criteria["text"] = opts.text;
317
+ }
318
+ if (opts.since) {
319
+ criteria["since"] = opts.since;
320
+ }
321
+ if (Object.keys(criteria).length === 0) {
322
+ throw new InboxError("VALIDATION_ERROR", "At least one search filter is required: --from, --subject, --body, or --text");
323
+ }
324
+ const uids = await client.search(criteria, { uid: true });
325
+ if (!uids.length) {
326
+ return [];
327
+ }
328
+ const sliced = uids.slice(-limit);
329
+ const results = [];
330
+ for await (const msg of client.fetch(sliced, {
331
+ uid: true,
332
+ flags: true,
333
+ envelope: true,
334
+ bodyStructure: true
335
+ }, { uid: true })) {
336
+ const envelope = msg.envelope;
337
+ if (!envelope) {
338
+ continue;
339
+ }
340
+ const flags = [...msg.flags ?? []];
341
+ results.push({
342
+ uid: msg.uid,
343
+ subject: envelope.subject ?? "(no subject)",
344
+ from: parseAddresses(envelope.from)[0] ?? { address: "" },
345
+ date: envelope.date ? new Date(envelope.date).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString(),
346
+ folder,
347
+ unread: !flags.includes("\\Seen"),
348
+ flagged: flags.includes("\\Flagged"),
349
+ hasAttachments: hasAttachmentParts(msg.bodyStructure)
350
+ });
351
+ }
352
+ return results.sort((a, b) => b.date.localeCompare(a.date));
353
+ });
354
+ }
355
+ async function moveMessage(config, uid, fromFolder, toFolder) {
356
+ return withImap(config, async (client) => {
357
+ await client.mailboxOpen(fromFolder).catch(() => {
358
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${fromFolder}" not found.`);
359
+ });
360
+ await client.messageMove([uid], toFolder, { uid: true });
361
+ });
362
+ }
363
+ async function markMessage(config, uid, folder, action) {
364
+ return withImap(config, async (client) => {
365
+ await client.mailboxOpen(folder).catch(() => {
366
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found.`);
367
+ });
368
+ switch (action) {
369
+ case "read":
370
+ await client.messageFlagsAdd([uid], ["\\Seen"], { uid: true });
371
+ break;
372
+ case "unread":
373
+ await client.messageFlagsRemove([uid], ["\\Seen"], { uid: true });
374
+ break;
375
+ case "flagged":
376
+ await client.messageFlagsAdd([uid], ["\\Flagged"], { uid: true });
377
+ break;
378
+ case "unflagged":
379
+ await client.messageFlagsRemove([uid], ["\\Flagged"], { uid: true });
380
+ break;
381
+ case "spam": {
382
+ const junk = await findJunkFolder(client);
383
+ if (junk) {
384
+ await client.messageMove([uid], junk, { uid: true });
385
+ } else {
386
+ await client.messageFlagsAdd([uid], ["\\Seen", "\\Flagged"], { uid: true });
387
+ }
388
+ break;
389
+ }
390
+ }
391
+ });
392
+ }
393
+ async function findJunkFolder(client) {
394
+ const list = await client.list();
395
+ const junk = list.find(
396
+ (f) => f.specialUse === "\\Junk" || /^(spam|junk|unwanted)/i.test(f.name)
397
+ );
398
+ return junk?.path ?? null;
399
+ }
400
+ async function deleteMessage(config, uid, folder) {
401
+ return withImap(config, async (client) => {
402
+ await client.mailboxOpen(folder).catch(() => {
403
+ throw new InboxError("IMAP_MAILBOX_NOT_FOUND", `Folder "${folder}" not found.`);
404
+ });
405
+ const trash = await findTrashFolder(client);
406
+ if (trash && trash !== folder) {
407
+ await client.messageMove([uid], trash, { uid: true });
408
+ } else {
409
+ await client.messageFlagsAdd([uid], ["\\Deleted"], { uid: true });
410
+ await client.mailboxClose();
411
+ }
412
+ });
413
+ }
414
+ async function findTrashFolder(client) {
415
+ const list = await client.list();
416
+ const trash = list.find(
417
+ (f) => f.specialUse === "\\Trash" || /^(trash|deleted|корзина)/i.test(f.name)
418
+ );
419
+ return trash?.path ?? null;
420
+ }
421
+ async function listFolders(config) {
422
+ return withImap(config, async (client) => {
423
+ const list = await client.list();
424
+ return list.map((f) => ({
425
+ name: f.name,
426
+ path: f.path,
427
+ flags: f.flags ? [...f.flags] : [],
428
+ specialUse: f.specialUse ?? void 0
429
+ }));
430
+ });
431
+ }
432
+ function createTransport(config) {
433
+ return nodemailer.createTransport({
434
+ host: config.smtp.host,
435
+ port: config.smtp.port,
436
+ secure: config.smtp.port === 465,
437
+ auth: { user: config.user, pass: config.pass },
438
+ connectionTimeout: 1e4,
439
+ socketTimeout: 1e4
440
+ });
441
+ }
442
+ function wrapSmtpError(err) {
443
+ if (err instanceof InboxError) {
444
+ return err;
445
+ }
446
+ const msg = err instanceof Error ? err.message : String(err);
447
+ const lower = msg.toLowerCase();
448
+ if (/authenticationfailed|auth failed|535/i.test(lower)) {
449
+ return new InboxError("SMTP_AUTH_FAILED", msg, err);
450
+ }
451
+ if (/econnrefused|enotfound|connect/i.test(lower)) {
452
+ return new InboxError("SMTP_CONNECT_FAILED", msg, err);
453
+ }
454
+ if (/invalid recipient|550|551|no such user/i.test(lower)) {
455
+ return new InboxError("SMTP_INVALID_RECIPIENT", msg, err);
456
+ }
457
+ return new InboxError("INTERNAL_ERROR", msg, err);
458
+ }
459
+ async function sendMessage(config, opts) {
460
+ const transport = createTransport(config);
461
+ try {
462
+ const info = await transport.sendMail({
463
+ from: opts.from ?? config.user,
464
+ to: opts.to,
465
+ cc: opts.cc,
466
+ bcc: opts.bcc,
467
+ subject: opts.subject,
468
+ text: opts.body
469
+ });
470
+ return { messageId: info.messageId ?? "" };
471
+ } catch (err) {
472
+ throw wrapSmtpError(err);
473
+ } finally {
474
+ transport.close();
475
+ }
476
+ }
477
+ async function replyMessage(config, original, body) {
478
+ const transport = createTransport(config);
479
+ const refs = [
480
+ ...original.originalReferences ?? [],
481
+ ...original.originalMessageId ? [original.originalMessageId] : []
482
+ ].join(" ");
483
+ const subject = original.originalSubject.startsWith("Re:") ? original.originalSubject : `Re: ${original.originalSubject}`;
484
+ try {
485
+ const info = await transport.sendMail({
486
+ from: config.user,
487
+ subject,
488
+ text: body,
489
+ inReplyTo: original.originalMessageId,
490
+ references: refs || void 0,
491
+ headers: {}
492
+ });
493
+ return { messageId: info.messageId ?? "" };
494
+ } catch (err) {
495
+ throw wrapSmtpError(err);
496
+ } finally {
497
+ transport.close();
498
+ }
499
+ }
500
+
501
+ export { InboxError, deleteMessage, getMessage, getThread, listAccounts, listFolders, listMessages, markMessage, moveMessage, replyMessage, resolveAccount, searchMessages, sendMessage };
502
+ //# sourceMappingURL=index.js.map
503
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts","../src/env.ts","../src/imap.ts","../src/smtp.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type InboxErrorCode =\n | 'ENV_MISSING' // INBOX_ACCOUNTS or account vars not set\n | 'ACCOUNT_NOT_FOUND' // --account X does not exist in config\n | 'IMAP_AUTH_FAILED' // wrong password or app-password not created\n | 'IMAP_CONNECT_FAILED' // cannot reach IMAP server\n | 'IMAP_TIMEOUT' // connection timed out\n | 'IMAP_MAILBOX_NOT_FOUND' // folder/mailbox does not exist\n | 'MESSAGE_NOT_FOUND' // uid not found in folder\n | 'SMTP_AUTH_FAILED' // SMTP auth error\n | 'SMTP_CONNECT_FAILED' // cannot reach SMTP server\n | 'SMTP_INVALID_RECIPIENT' // invalid recipient address\n | 'VALIDATION_ERROR' // bad flags or arguments\n | 'INTERNAL_ERROR'; // unexpected error\n\nexport class InboxError extends Error {\n override readonly name = 'InboxError';\n\n constructor(\n public readonly code: InboxErrorCode,\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n }\n}\n","import { useEnv } from '@kb-labs/sdk';\nimport type { AccountInfo } from '@kb-labs/inbox-contracts';\nimport { InboxError } from './error.js';\n\nexport interface AccountConfig {\n name: string;\n user: string;\n pass: string;\n imap: { host: string; port: number };\n smtp: { host: string; port: number };\n}\n\n/**\n * Returns a list of account names from INBOX_ACCOUNTS env var.\n * INBOX_ACCOUNTS=work,personal\n */\nfunction getAccountNames(): string[] {\n const raw = useEnv('INBOX_ACCOUNTS');\n if (!raw) { return []; }\n return raw.split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\n}\n\n/**\n * Read config for a single named account from env vars.\n * Vars: INBOX_ACCOUNT_{NAME}_USER, INBOX_ACCOUNT_{NAME}_PASS,\n * INBOX_ACCOUNT_{NAME}_IMAP_HOST, INBOX_ACCOUNT_{NAME}_IMAP_PORT,\n * INBOX_ACCOUNT_{NAME}_SMTP_HOST, INBOX_ACCOUNT_{NAME}_SMTP_PORT\n */\nfunction readAccountConfig(name: string): AccountConfig {\n const prefix = `INBOX_ACCOUNT_${name}`;\n const user = useEnv(`${prefix}_USER`);\n const pass = useEnv(`${prefix}_PASS`);\n const imapHost = useEnv(`${prefix}_IMAP_HOST`);\n const smtpHost = useEnv(`${prefix}_SMTP_HOST`);\n\n if (!user || !pass || !imapHost || !smtpHost) {\n const missing = [\n !user && `${prefix}_USER`,\n !pass && `${prefix}_PASS`,\n !imapHost && `${prefix}_IMAP_HOST`,\n !smtpHost && `${prefix}_SMTP_HOST`,\n ].filter(Boolean).join(', ');\n\n throw new InboxError(\n 'ENV_MISSING',\n `Account \"${name.toLowerCase()}\" is missing required env vars: ${missing}`,\n );\n }\n\n const imapPortRaw = useEnv(`${prefix}_IMAP_PORT`) ?? '993';\n const smtpPortRaw = useEnv(`${prefix}_SMTP_PORT`) ?? '465';\n\n return {\n name: name.toLowerCase(),\n user,\n pass,\n imap: { host: imapHost, port: parseInt(imapPortRaw, 10) },\n smtp: { host: smtpHost, port: parseInt(smtpPortRaw, 10) },\n };\n}\n\n/**\n * Resolve account config by name or index (default: first account).\n * Throws InboxError with actionable hints if not found.\n */\nexport function resolveAccount(nameOrIndex?: string): AccountConfig {\n const names = getAccountNames();\n\n if (names.length === 0) {\n throw new InboxError(\n 'ENV_MISSING',\n 'No accounts configured. Set INBOX_ACCOUNTS=work,personal and account vars.',\n );\n }\n\n if (!nameOrIndex) {\n // Default: first account\n return readAccountConfig(names[0]!);\n }\n\n // Try by name first (case-insensitive)\n const byName = names.find(n => n === nameOrIndex.toUpperCase());\n if (byName) { return readAccountConfig(byName); }\n\n // Try by index\n const idx = parseInt(nameOrIndex, 10);\n if (!isNaN(idx) && idx >= 0 && idx < names.length) {\n return readAccountConfig(names[idx]!);\n }\n\n throw new InboxError(\n 'ACCOUNT_NOT_FOUND',\n `Account \"${nameOrIndex}\" not found. Available: ${names.map(n => n.toLowerCase()).join(', ')}`,\n );\n}\n\n/**\n * Returns info for all configured accounts (no passwords).\n */\nexport function listAccounts(): AccountInfo[] {\n const names = getAccountNames();\n return names.map((name, index) => {\n const prefix = `INBOX_ACCOUNT_${name}`;\n const user = useEnv(`${prefix}_USER`) ?? '(not set)';\n const imapHost = useEnv(`${prefix}_IMAP_HOST`) ?? '(not set)';\n const imapPortRaw = useEnv(`${prefix}_IMAP_PORT`) ?? '993';\n return {\n name: name.toLowerCase(),\n user,\n imapHost,\n imapPort: parseInt(imapPortRaw, 10),\n // index for --account flag\n _index: index,\n } as AccountInfo & { _index: number };\n });\n}\n","import { ImapFlow, type FetchMessageObject } from 'imapflow';\nimport type { Email, EmailSlim, Folder } from '@kb-labs/inbox-contracts';\nimport type { AccountConfig } from './env.js';\nimport { InboxError } from './error.js';\n\n// ─── connection helper ────────────────────────────────────────────────────────\n\nasync function withImap<T>(config: AccountConfig, fn: (client: ImapFlow) => Promise<T>): Promise<T> {\n const client = new ImapFlow({\n host: config.imap.host,\n port: config.imap.port,\n secure: true,\n auth: { user: config.user, pass: config.pass },\n logger: false,\n // Fail fast — CLI commands should not hang\n connectionTimeout: 10_000,\n });\n\n try {\n await client.connect();\n } catch (err) {\n throw wrapImapError(err);\n }\n\n try {\n return await fn(client);\n } catch (err) {\n throw err instanceof InboxError ? err : wrapImapError(err);\n } finally {\n try { await client.logout(); } catch { /* ignore logout errors */ }\n }\n}\n\nfunction wrapImapError(err: unknown): InboxError {\n if (err instanceof InboxError) { return err; }\n\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n\n if (/authenticationfailed|invalid credentials|login failed/i.test(lower)) {\n return new InboxError('IMAP_AUTH_FAILED', msg, err);\n }\n if (/econnrefused|enotfound|etimedout|connect/i.test(lower)) {\n return new InboxError('IMAP_CONNECT_FAILED', msg, err);\n }\n if (/timeout/i.test(lower)) {\n return new InboxError('IMAP_TIMEOUT', msg, err);\n }\n if (/no such mailbox|mailbox not found|doesn't exist/i.test(lower)) {\n return new InboxError('IMAP_MAILBOX_NOT_FOUND', msg, err);\n }\n\n return new InboxError('INTERNAL_ERROR', msg, err);\n}\n\n// ─── address parsing ──────────────────────────────────────────────────────────\n\nfunction parseAddresses(raw: unknown): Array<{ name?: string; address: string }> {\n if (!raw || !Array.isArray(raw)) { return []; }\n return (raw as Array<{ name?: string; address?: string }>)\n .filter(a => a.address)\n .map(a => ({ ...(a.name ? { name: a.name } : {}), address: a.address! }));\n}\n\n// ─── list ─────────────────────────────────────────────────────────────────────\n\nexport interface ListMessagesOptions {\n folder?: string;\n unreadOnly?: boolean;\n since?: Date;\n limit?: number;\n}\n\nexport async function listMessages(config: AccountConfig, opts: ListMessagesOptions = {}): Promise<EmailSlim[]> {\n const folder = opts.folder ?? 'INBOX';\n const limit = opts.limit ?? 50;\n\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found. Use kb inbox folders to list available folders.`);\n });\n\n // Build search criteria\n const searchCriteria: Record<string, unknown> = {};\n if (opts.unreadOnly) { searchCriteria['unseen'] = true; }\n if (opts.since) { searchCriteria['since'] = opts.since; }\n\n const hasFilters = Object.keys(searchCriteria).length > 0;\n const uids: number[] = hasFilters\n ? await client.search(searchCriteria, { uid: true }) as number[]\n : await client.search({ all: true }, { uid: true }) as number[];\n\n if (!uids.length) { return []; }\n\n // Take last N (most recent) — IMAP UIDs are ascending\n const sliced = uids.slice(-limit);\n\n const results: EmailSlim[] = [];\n\n for await (const msg of client.fetch(sliced, {\n uid: true,\n flags: true,\n envelope: true,\n bodyStructure: true,\n }, { uid: true })) {\n const envelope = (msg as FetchMessageObject).envelope;\n if (!envelope) { continue; }\n\n const flags = [...((msg as FetchMessageObject).flags ?? [])];\n const hasAttachments = hasAttachmentParts((msg as FetchMessageObject).bodyStructure);\n\n results.push({\n uid: (msg as FetchMessageObject).uid,\n subject: envelope.subject ?? '(no subject)',\n from: parseAddresses(envelope.from)[0] ?? { address: '' },\n date: envelope.date ? new Date(envelope.date).toISOString() : new Date(0).toISOString(),\n folder,\n unread: !flags.includes('\\\\Seen'),\n flagged: flags.includes('\\\\Flagged'),\n hasAttachments,\n });\n }\n\n // Sort most recent first\n return results.sort((a, b) => b.date.localeCompare(a.date));\n });\n}\n\nfunction hasAttachmentParts(structure: unknown): boolean {\n if (!structure) { return false; }\n const s = structure as { type?: string; disposition?: { type?: string }; childNodes?: unknown[] };\n if (s.disposition?.type?.toLowerCase() === 'attachment') { return true; }\n if (s.childNodes) { return s.childNodes.some(hasAttachmentParts); }\n return false;\n}\n\n// ─── get ──────────────────────────────────────────────────────────────────────\n\nexport interface GetMessageOptions {\n folder?: string;\n withAttachments?: boolean;\n}\n\nexport async function getMessage(config: AccountConfig, uid: number, opts: GetMessageOptions = {}): Promise<Email> {\n const folder = opts.folder ?? 'INBOX';\n\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found.`);\n });\n\n let found: Email | null = null;\n\n for await (const msg of client.fetch([uid], {\n uid: true,\n flags: true,\n envelope: true,\n source: true,\n }, { uid: true })) {\n const envelope = (msg as FetchMessageObject).envelope;\n if (!envelope) { continue; }\n\n const flags = [...((msg as FetchMessageObject).flags ?? [])];\n const source = (msg as FetchMessageObject).source?.toString('utf8') ?? '';\n\n // Basic text extraction from raw source\n const { text, html, attachments } = parseBody(source);\n\n found = {\n uid: (msg as FetchMessageObject).uid,\n messageId: envelope.messageId ?? undefined,\n subject: envelope.subject ?? '(no subject)',\n from: parseAddresses(envelope.from)[0] ?? { address: '' },\n to: parseAddresses(envelope.to),\n cc: parseAddresses(envelope.cc),\n date: envelope.date ? new Date(envelope.date).toISOString() : new Date(0).toISOString(),\n folder,\n flags,\n text,\n html,\n attachments: opts.withAttachments ? attachments : undefined,\n inReplyTo: envelope.inReplyTo ?? undefined,\n references: (envelope as unknown as { references?: string[] }).references ?? undefined,\n };\n }\n\n if (!found) {\n throw new InboxError('MESSAGE_NOT_FOUND', `Message uid=${uid} not found in \"${folder}\". Refresh with: kb inbox list --folder ${folder}`);\n }\n\n return found;\n });\n}\n\nfunction parseBody(source: string): { text?: string; html?: string; attachments: Array<{ filename: string; contentType: string; size: number }> } {\n // Minimal MIME parsing — extract plain text and html parts\n // For production quality, a proper MIME parser (mailparser) would be used\n // This gives agents usable text content from the raw source\n const textMatch = source.match(/Content-Type: text\\/plain[^\\r\\n]*[\\r\\n]+([\\s\\S]*?)(?=\\r?\\n--|\\r?\\n\\r?\\n--)/i);\n const htmlMatch = source.match(/Content-Type: text\\/html[^\\r\\n]*[\\r\\n]+([\\s\\S]*?)(?=\\r?\\n--|\\r?\\n\\r?\\n--)/i);\n\n const text = textMatch?.[1] != null ? decodeBodyPart(textMatch[1]) : undefined;\n const html = htmlMatch?.[1] != null ? decodeBodyPart(htmlMatch[1]) : undefined;\n\n const attachments: Array<{ filename: string; contentType: string; size: number }> = [];\n const attachRe = /Content-Disposition: attachment[^\\r\\n]*(?:\\r?\\n[^\\r\\n]+)*\\r?\\n\\s*filename=\"([^\"]+)\"/gi;\n const typeRe = /Content-Type: ([^\\s;]+)/i;\n let m: RegExpExecArray | null;\n while ((m = attachRe.exec(source)) !== null) {\n const filename = m[1] ?? '';\n const typeMatch = source.slice(Math.max(0, m.index - 200), m.index).match(typeRe);\n attachments.push({ filename, contentType: typeMatch?.[1] ?? 'application/octet-stream', size: 0 });\n }\n\n return { text, html, attachments };\n}\n\nfunction decodeBodyPart(raw: string): string {\n // Strip transfer-encoding headers and return body\n return raw.replace(/^[^\\r\\n]*[\\r\\n]+/gm, '').trim();\n}\n\n// ─── thread ───────────────────────────────────────────────────────────────────\n\nexport async function getThread(config: AccountConfig, uid: number, opts: GetMessageOptions = {}): Promise<Email[]> {\n const folder = opts.folder ?? 'INBOX';\n\n const root = await getMessage(config, uid, { folder });\n\n if (!root.inReplyTo && !root.messageId) { return [root]; }\n\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found.`);\n });\n\n // Search for messages with matching references\n const targetId = root.messageId ?? root.inReplyTo;\n if (!targetId) { return [root]; }\n\n const uids = await client.search({ header: { 'References': targetId } }, { uid: true }) as number[];\n const selfUids = await client.search({ header: { 'Message-ID': targetId } }, { uid: true }) as number[];\n\n const allUids = [...new Set([...uids, ...selfUids, uid])].sort((a, b) => a - b);\n\n const messages: Email[] = [];\n for (const u of allUids) {\n try {\n const msg = await getMessage(config, u, { folder });\n messages.push(msg);\n } catch {\n // Skip individual missing messages\n }\n }\n\n return messages.sort((a, b) => a.date.localeCompare(b.date));\n });\n}\n\n// ─── search ───────────────────────────────────────────────────────────────────\n\nexport interface SearchMessagesOptions {\n from?: string;\n subject?: string;\n body?: string;\n text?: string;\n folder?: string;\n since?: Date;\n limit?: number;\n}\n\nexport async function searchMessages(config: AccountConfig, opts: SearchMessagesOptions): Promise<EmailSlim[]> {\n const folder = opts.folder ?? 'INBOX';\n const limit = opts.limit ?? 20;\n\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found.`);\n });\n\n const criteria: Record<string, unknown> = {};\n if (opts.from) { criteria['from'] = opts.from; }\n if (opts.subject) { criteria['subject'] = opts.subject; }\n if (opts.body) { criteria['body'] = opts.body; }\n if (opts.text) { criteria['text'] = opts.text; }\n if (opts.since) { criteria['since'] = opts.since; }\n\n if (Object.keys(criteria).length === 0) {\n throw new InboxError('VALIDATION_ERROR', 'At least one search filter is required: --from, --subject, --body, or --text');\n }\n\n const uids = await client.search(criteria, { uid: true }) as number[];\n if (!uids.length) { return []; }\n\n const sliced = uids.slice(-limit);\n const results: EmailSlim[] = [];\n\n for await (const msg of client.fetch(sliced, {\n uid: true,\n flags: true,\n envelope: true,\n bodyStructure: true,\n }, { uid: true })) {\n const envelope = (msg as FetchMessageObject).envelope;\n if (!envelope) { continue; }\n\n const flags = [...((msg as FetchMessageObject).flags ?? [])];\n results.push({\n uid: (msg as FetchMessageObject).uid,\n subject: envelope.subject ?? '(no subject)',\n from: parseAddresses(envelope.from)[0] ?? { address: '' },\n date: envelope.date ? new Date(envelope.date).toISOString() : new Date(0).toISOString(),\n folder,\n unread: !flags.includes('\\\\Seen'),\n flagged: flags.includes('\\\\Flagged'),\n hasAttachments: hasAttachmentParts((msg as FetchMessageObject).bodyStructure),\n });\n }\n\n return results.sort((a, b) => b.date.localeCompare(a.date));\n });\n}\n\n// ─── move ─────────────────────────────────────────────────────────────────────\n\nexport async function moveMessage(config: AccountConfig, uid: number, fromFolder: string, toFolder: string): Promise<void> {\n return withImap(config, async client => {\n await client.mailboxOpen(fromFolder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${fromFolder}\" not found.`);\n });\n\n await client.messageMove([uid], toFolder, { uid: true });\n });\n}\n\n// ─── mark ─────────────────────────────────────────────────────────────────────\n\nexport type MarkAction = 'read' | 'unread' | 'spam' | 'flagged' | 'unflagged';\n\nexport async function markMessage(config: AccountConfig, uid: number, folder: string, action: MarkAction): Promise<void> {\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found.`);\n });\n\n switch (action) {\n case 'read':\n await client.messageFlagsAdd([uid], ['\\\\Seen'], { uid: true });\n break;\n case 'unread':\n await client.messageFlagsRemove([uid], ['\\\\Seen'], { uid: true });\n break;\n case 'flagged':\n await client.messageFlagsAdd([uid], ['\\\\Flagged'], { uid: true });\n break;\n case 'unflagged':\n await client.messageFlagsRemove([uid], ['\\\\Flagged'], { uid: true });\n break;\n case 'spam': {\n // Move to Junk/Spam folder and mark as seen\n const junk = await findJunkFolder(client);\n if (junk) {\n await client.messageMove([uid], junk, { uid: true });\n } else {\n await client.messageFlagsAdd([uid], ['\\\\Seen', '\\\\Flagged'], { uid: true });\n }\n break;\n }\n }\n });\n}\n\nasync function findJunkFolder(client: ImapFlow): Promise<string | null> {\n const list = await client.list();\n const junk = list.find(f =>\n f.specialUse === '\\\\Junk' ||\n /^(spam|junk|unwanted)/i.test(f.name),\n );\n return junk?.path ?? null;\n}\n\n// ─── delete ───────────────────────────────────────────────────────────────────\n\nexport async function deleteMessage(config: AccountConfig, uid: number, folder: string): Promise<void> {\n return withImap(config, async client => {\n await client.mailboxOpen(folder).catch(() => {\n throw new InboxError('IMAP_MAILBOX_NOT_FOUND', `Folder \"${folder}\" not found.`);\n });\n\n // Move to Trash if available, otherwise flag as deleted and expunge\n const trash = await findTrashFolder(client);\n if (trash && trash !== folder) {\n await client.messageMove([uid], trash, { uid: true });\n } else {\n await client.messageFlagsAdd([uid], ['\\\\Deleted'], { uid: true });\n await client.mailboxClose();\n }\n });\n}\n\nasync function findTrashFolder(client: ImapFlow): Promise<string | null> {\n const list = await client.list();\n const trash = list.find(f =>\n f.specialUse === '\\\\Trash' ||\n /^(trash|deleted|корзина)/i.test(f.name),\n );\n return trash?.path ?? null;\n}\n\n// ─── folders ─────────────────────────────────────────────────────────────────\n\nexport async function listFolders(config: AccountConfig): Promise<Folder[]> {\n return withImap(config, async client => {\n const list = await client.list();\n return list.map(f => ({\n name: f.name,\n path: f.path,\n flags: f.flags ? [...f.flags] : [],\n specialUse: f.specialUse ?? undefined,\n }));\n });\n}\n","import nodemailer from 'nodemailer';\nimport type { AccountConfig } from './env.js';\nimport { InboxError } from './error.js';\n\nexport interface SendOptions {\n to: string;\n subject: string;\n body: string;\n cc?: string;\n bcc?: string;\n from?: string;\n}\n\nexport interface ReplyOptions {\n body: string;\n originalMessageId?: string;\n originalReferences?: string[];\n originalSubject: string;\n}\n\nfunction createTransport(config: AccountConfig) {\n return nodemailer.createTransport({\n host: config.smtp.host,\n port: config.smtp.port,\n secure: config.smtp.port === 465,\n auth: { user: config.user, pass: config.pass },\n connectionTimeout: 10_000,\n socketTimeout: 10_000,\n });\n}\n\nfunction wrapSmtpError(err: unknown): InboxError {\n if (err instanceof InboxError) { return err; }\n\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n\n if (/authenticationfailed|auth failed|535/i.test(lower)) {\n return new InboxError('SMTP_AUTH_FAILED', msg, err);\n }\n if (/econnrefused|enotfound|connect/i.test(lower)) {\n return new InboxError('SMTP_CONNECT_FAILED', msg, err);\n }\n if (/invalid recipient|550|551|no such user/i.test(lower)) {\n return new InboxError('SMTP_INVALID_RECIPIENT', msg, err);\n }\n\n return new InboxError('INTERNAL_ERROR', msg, err);\n}\n\nexport async function sendMessage(config: AccountConfig, opts: SendOptions): Promise<{ messageId: string }> {\n const transport = createTransport(config);\n\n try {\n const info = await transport.sendMail({\n from: opts.from ?? config.user,\n to: opts.to,\n cc: opts.cc,\n bcc: opts.bcc,\n subject: opts.subject,\n text: opts.body,\n });\n\n return { messageId: info.messageId ?? '' };\n } catch (err) {\n throw wrapSmtpError(err);\n } finally {\n transport.close();\n }\n}\n\nexport async function replyMessage(config: AccountConfig, original: ReplyOptions, body: string): Promise<{ messageId: string }> {\n const transport = createTransport(config);\n\n // Build References header: original References + original Message-ID\n const refs = [\n ...(original.originalReferences ?? []),\n ...(original.originalMessageId ? [original.originalMessageId] : []),\n ].join(' ');\n\n const subject = original.originalSubject.startsWith('Re:')\n ? original.originalSubject\n : `Re: ${original.originalSubject}`;\n\n try {\n const info = await transport.sendMail({\n from: config.user,\n subject,\n text: body,\n inReplyTo: original.originalMessageId,\n references: refs || undefined,\n headers: {},\n });\n\n return { messageId: info.messageId ?? '' };\n } catch (err) {\n throw wrapSmtpError(err);\n } finally {\n transport.close();\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@kb-labs/inbox-core",
3
+ "version": "2.96.0",
4
+ "type": "module",
5
+ "description": "Business logic for inbox plugin (IMAP/SMTP, pure, testable in isolation)",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "dependencies": {
19
+ "imapflow": "^1.0.0",
20
+ "nodemailer": "^8.0.7",
21
+ "@kb-labs/sdk": "2.21.2",
22
+ "@kb-labs/inbox-contracts": "2.96.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.0.0",
26
+ "@types/nodemailer": "^8.0.0",
27
+ "rimraf": "^6.0.1",
28
+ "tsup": "^8.5.0",
29
+ "typescript": "^5.6.3",
30
+ "vitest": "^3.2.6",
31
+ "@kb-labs/devkit": "2.96.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.0.0",
35
+ "pnpm": ">=9.0.0"
36
+ },
37
+ "scripts": {
38
+ "clean": "rimraf dist",
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "lint": "eslint src --ext .ts",
42
+ "lint:fix": "eslint . --fix",
43
+ "type-check": "tsc --noEmit",
44
+ "test": "vitest run --passWithNoTests"
45
+ }
46
+ }