@bobfrankston/mailx-imap 0.1.24 → 0.1.26
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/index.d.ts +5 -27
- package/index.js +56 -132
- package/package.json +5 -5
package/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { TransportFactory } from "@bobfrankston/tcp-transport";
|
|
7
7
|
import { MailxDB, FileMessageStore } from "@bobfrankston/mailx-store";
|
|
8
|
-
import type { AccountConfig, MessageEnvelope,
|
|
8
|
+
import type { AccountConfig, MessageEnvelope, Folder } from "@bobfrankston/mailx-types";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
10
|
/** Events emitted by the IMAP manager */
|
|
11
11
|
export interface ImapManagerEvents {
|
|
@@ -247,7 +247,10 @@ export declare class ImapManager extends EventEmitter {
|
|
|
247
247
|
stopPeriodicSync(): void;
|
|
248
248
|
/** Check if an account is OAuth (Gmail/Outlook — generous connection limits) */
|
|
249
249
|
isOAuthAccount(accountId: string): boolean;
|
|
250
|
-
/** Start IMAP IDLE
|
|
250
|
+
/** Start an IMAP IDLE watcher per account on INBOX. Other folders are
|
|
251
|
+
* not special-cased here — sync runs uniformly on every folder via the
|
|
252
|
+
* periodic full-sync timer (STATUS-before-SELECT optimization is the
|
|
253
|
+
* right way to make that cheap, not piling per-folder IDLE sockets). */
|
|
251
254
|
startWatching(): Promise<void>;
|
|
252
255
|
/** Stop all IDLE watchers */
|
|
253
256
|
stopWatching(): Promise<void>;
|
|
@@ -336,31 +339,6 @@ export declare class ImapManager extends EventEmitter {
|
|
|
336
339
|
processSyncActions(accountId: string): Promise<void>;
|
|
337
340
|
/** Find a folder by specialUse, case-insensitive */
|
|
338
341
|
private findFolder;
|
|
339
|
-
/** Optimistic local-first Sent insert: write a row into the local DB's
|
|
340
|
-
* Sent folder the moment the user hits Send, so the list reflects it
|
|
341
|
-
* immediately instead of waiting for SMTP + IMAP-APPEND + syncFolder
|
|
342
|
-
* (five server round-trips against a Dovecot that caps at 20 conns).
|
|
343
|
-
*
|
|
344
|
-
* Uses a synthetic negative UID so it can't collide with a real APPENDUID
|
|
345
|
-
* (which is always positive). When the real sync eventually picks the
|
|
346
|
-
* message up in Sent with the server's UID, `db.upsertMessage` spots
|
|
347
|
-
* the Message-ID match and rebinds the existing row's UID — no duplicate.
|
|
348
|
-
* Negative UID also makes the row render pink (getMessages flags uid<0
|
|
349
|
-
* as pending) so the user sees it's not-yet-reconciled.
|
|
350
|
-
*
|
|
351
|
-
* Best-effort — any failure path (no Sent folder yet, parse error, store
|
|
352
|
-
* write error) is logged and swallowed; the send itself is unaffected. */
|
|
353
|
-
insertOptimisticSentRow(accountId: string, envelope: {
|
|
354
|
-
messageId: string;
|
|
355
|
-
inReplyTo: string;
|
|
356
|
-
references: string[];
|
|
357
|
-
subject: string;
|
|
358
|
-
from: EmailAddress;
|
|
359
|
-
to: EmailAddress[];
|
|
360
|
-
cc: EmailAddress[];
|
|
361
|
-
bcc: EmailAddress[];
|
|
362
|
-
date: number;
|
|
363
|
-
}, rawMessage: string): Promise<void>;
|
|
364
342
|
/** Copy sent message to the Sent folder via IMAP APPEND */
|
|
365
343
|
copyToSent(accountId: string, rawMessage: string | Buffer): Promise<void>;
|
|
366
344
|
/** Save a draft to the Drafts folder via IMAP APPEND.
|
package/index.js
CHANGED
|
@@ -874,8 +874,40 @@ export class ImapManager extends EventEmitter {
|
|
|
874
874
|
if (!folder)
|
|
875
875
|
throw new Error(`Folder ${folderId} not found`);
|
|
876
876
|
this.emit("syncProgress", accountId, `sync:${folder.path}`, 0);
|
|
877
|
-
// Get the highest UID we already have for this folder
|
|
877
|
+
// Get the highest UID we already have for this folder. IMAP UIDs are
|
|
878
|
+
// monotonically increasing within a UIDVALIDITY (RFC 3501); a
|
|
879
|
+
// high-water mark is the right anchor for incremental fetch.
|
|
878
880
|
const highestUid = this.db.getHighestUid(accountId, folderId);
|
|
881
|
+
// STATUS-before-SELECT: ask the server cheaply whether anything has
|
|
882
|
+
// changed (UIDNEXT moved or message count differs). STATUS doesn't
|
|
883
|
+
// load the mailbox index, doesn't lock the mailbox, doesn't risk the
|
|
884
|
+
// SELECT-wedge that's been hammering bobma. Only when STATUS says
|
|
885
|
+
// there's actual work to do do we fall through to SELECT+FETCH.
|
|
886
|
+
// Apply uniformly to every folder — no special-casing.
|
|
887
|
+
try {
|
|
888
|
+
if (typeof client.getStatus === "function") {
|
|
889
|
+
const status = await client.getStatus(folder.path);
|
|
890
|
+
const serverHighest = (status.uidNext || 1) - 1;
|
|
891
|
+
const serverCount = status.messages ?? -1;
|
|
892
|
+
const localCount = this.db.getMessageCount(accountId, folderId);
|
|
893
|
+
const isUpToDate = highestUid > 0
|
|
894
|
+
&& serverHighest <= highestUid
|
|
895
|
+
&& serverCount >= 0
|
|
896
|
+
&& serverCount === localCount;
|
|
897
|
+
if (isUpToDate) {
|
|
898
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS up-to-date (uidNext=${status.uidNext}, server=${serverCount}, local=${localCount}) — skipping SELECT`);
|
|
899
|
+
this.emit("syncProgress", accountId, `sync:${folder.path}`, 100);
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS uidNext=${status.uidNext} server=${serverCount} local=${localCount} highestUid=${highestUid} — needs SELECT`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (e) {
|
|
906
|
+
// STATUS shouldn't fail, but don't make it load-bearing — fall
|
|
907
|
+
// through to the SELECT path on any error and let that path's
|
|
908
|
+
// existing handling deal with it.
|
|
909
|
+
console.log(` [sync] ${accountId}/${folder.path}: STATUS failed (${e?.message || e}) — falling through to SELECT`);
|
|
910
|
+
}
|
|
879
911
|
console.log(` [sync] ${accountId}/${folder.path}: highestUid=${highestUid}, fetching...`);
|
|
880
912
|
let messages;
|
|
881
913
|
const firstSync = highestUid === 0;
|
|
@@ -1106,7 +1138,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1106
1138
|
const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
|
|
1107
1139
|
console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
|
|
1108
1140
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
1109
|
-
this.db.deleteMessage(accountId, uid);
|
|
1141
|
+
this.db.deleteMessage(accountId, uid, "reconcile: server returned UID list without this row", `mailx-imap syncFolder reconcile (${folder.path})`);
|
|
1110
1142
|
deletedCount++;
|
|
1111
1143
|
}
|
|
1112
1144
|
if (deletedCount > 0)
|
|
@@ -1482,7 +1514,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1482
1514
|
const tag = env ? `msgid=${env.messageId || "?"} subj="${(env.subject || "").slice(0, 60)}"` : "unknown";
|
|
1483
1515
|
console.log(` [reconcile-delete] ${accountId}/${folder.path} uid=${uid} ${tag}`);
|
|
1484
1516
|
this.unlinkBodyFile(accountId, uid, folder.id).catch(() => { });
|
|
1485
|
-
this.db.deleteMessage(accountId, uid);
|
|
1517
|
+
this.db.deleteMessage(accountId, uid, "Gmail-API reconcile: server list missing this UID", `mailx-imap Gmail reconcile (${folder.path})`);
|
|
1486
1518
|
}
|
|
1487
1519
|
if (toDelete.length > 0)
|
|
1488
1520
|
console.log(` [api] ${accountId}/${folder.path}: ${toDelete.length} deleted`);
|
|
@@ -1861,7 +1893,10 @@ export class ImapManager extends EventEmitter {
|
|
|
1861
1893
|
const config = this.configs.get(accountId);
|
|
1862
1894
|
return !!config?.tokenProvider;
|
|
1863
1895
|
}
|
|
1864
|
-
/** Start IMAP IDLE
|
|
1896
|
+
/** Start an IMAP IDLE watcher per account on INBOX. Other folders are
|
|
1897
|
+
* not special-cased here — sync runs uniformly on every folder via the
|
|
1898
|
+
* periodic full-sync timer (STATUS-before-SELECT optimization is the
|
|
1899
|
+
* right way to make that cheap, not piling per-folder IDLE sockets). */
|
|
1865
1900
|
async startWatching() {
|
|
1866
1901
|
for (const [accountId] of this.configs) {
|
|
1867
1902
|
if (this.watchers.has(accountId))
|
|
@@ -1871,70 +1906,16 @@ export class ImapManager extends EventEmitter {
|
|
|
1871
1906
|
// is parked in IDLE, it's unusable for any other command, so
|
|
1872
1907
|
// it can't share the ops queue. Counts against the per-host
|
|
1873
1908
|
// semaphore (one slot for the IDLE socket).
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
// full sync.
|
|
1880
|
-
const stops = [];
|
|
1881
|
-
const clients = [];
|
|
1882
|
-
const watchOne = async (mailboxLabel, path) => {
|
|
1883
|
-
const client = await this.createClient(accountId, "idle");
|
|
1884
|
-
clients.push(client);
|
|
1885
|
-
const stop = await client.watchMailbox(path, (newCount) => {
|
|
1886
|
-
console.log(` [idle] ${accountId} ${path}: ${newCount} new message(s)`);
|
|
1887
|
-
if (mailboxLabel === "inbox") {
|
|
1888
|
-
// Fast path: incremental fetch of NEW UIDs only.
|
|
1889
|
-
// Heavy reconcile runs on the 5-minute STATUS poll.
|
|
1890
|
-
this.syncInboxNewOnly(accountId).catch(e => console.error(` [idle] inbox sync error: ${e.message}`));
|
|
1891
|
-
}
|
|
1892
|
-
else {
|
|
1893
|
-
// Sent / Drafts changed elsewhere. Use the
|
|
1894
|
-
// standard folder sync — picks up the new UID,
|
|
1895
|
-
// rebinds any optimistic local row by Message-ID.
|
|
1896
|
-
const folder = this.findFolder(accountId, mailboxLabel);
|
|
1897
|
-
if (folder) {
|
|
1898
|
-
this.syncFolder(accountId, folder.id).catch(e => console.error(` [idle] ${path} sync error: ${e.message}`));
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
stops.push(stop);
|
|
1903
|
-
};
|
|
1904
|
-
await watchOne("inbox", "INBOX");
|
|
1905
|
-
const sent = this.findFolder(accountId, "sent");
|
|
1906
|
-
if (sent) {
|
|
1907
|
-
try {
|
|
1908
|
-
await watchOne("sent", sent.path);
|
|
1909
|
-
}
|
|
1910
|
-
catch (e) {
|
|
1911
|
-
console.error(` [idle] Failed to watch ${sent.path}: ${e.message}`);
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
const drafts = this.findFolder(accountId, "drafts");
|
|
1915
|
-
if (drafts) {
|
|
1916
|
-
try {
|
|
1917
|
-
await watchOne("drafts", drafts.path);
|
|
1918
|
-
}
|
|
1919
|
-
catch (e) {
|
|
1920
|
-
console.error(` [idle] Failed to watch ${drafts.path}: ${e.message}`);
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1909
|
+
const watchClient = await this.createClient(accountId, "idle");
|
|
1910
|
+
const stop = await watchClient.watchMailbox("INBOX", (newCount) => {
|
|
1911
|
+
console.log(` [idle] ${accountId}: ${newCount} new message(s)`);
|
|
1912
|
+
this.syncInboxNewOnly(accountId).catch(e => console.error(` [idle] sync error: ${e.message}`));
|
|
1913
|
+
});
|
|
1923
1914
|
this.watchers.set(accountId, async () => {
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
await stop();
|
|
1927
|
-
}
|
|
1928
|
-
catch { /* ignore */ }
|
|
1929
|
-
}
|
|
1930
|
-
for (const c of clients) {
|
|
1931
|
-
try {
|
|
1932
|
-
await c.logout();
|
|
1933
|
-
}
|
|
1934
|
-
catch { /* ignore */ }
|
|
1935
|
-
}
|
|
1915
|
+
await stop();
|
|
1916
|
+
await watchClient.logout();
|
|
1936
1917
|
});
|
|
1937
|
-
console.log(` [idle] Watching INBOX
|
|
1918
|
+
console.log(` [idle] Watching INBOX for ${accountId}`);
|
|
1938
1919
|
}
|
|
1939
1920
|
catch (e) {
|
|
1940
1921
|
console.error(` [idle] Failed to watch ${accountId}: ${e.message}`);
|
|
@@ -2202,7 +2183,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2202
2183
|
continue;
|
|
2203
2184
|
try {
|
|
2204
2185
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2205
|
-
this.db.deleteMessage(accountId, uid);
|
|
2186
|
+
this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (Gmail batch)");
|
|
2206
2187
|
counters.deleted++;
|
|
2207
2188
|
madeProgress = true;
|
|
2208
2189
|
}
|
|
@@ -2302,7 +2283,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2302
2283
|
continue;
|
|
2303
2284
|
try {
|
|
2304
2285
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2305
|
-
this.db.deleteMessage(accountId, uid);
|
|
2286
|
+
this.db.deleteMessage(accountId, uid, "prefetch batch: server didn't return body for queued UID — assumed deleted", "mailx-imap prefetchBodies (IMAP batch)");
|
|
2306
2287
|
counters.deleted++;
|
|
2307
2288
|
madeProgress = true;
|
|
2308
2289
|
}
|
|
@@ -2338,7 +2319,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2338
2319
|
// Local first — remove all from DB immediately
|
|
2339
2320
|
for (const msg of messages) {
|
|
2340
2321
|
this.unlinkBodyFile(accountId, msg.uid, msg.folderId).catch(() => { });
|
|
2341
|
-
this.db.deleteMessage(accountId, msg.uid);
|
|
2322
|
+
this.db.deleteMessage(accountId, msg.uid, "user-initiated delete (bulk)", "mailx-imap deleteMessages");
|
|
2342
2323
|
}
|
|
2343
2324
|
console.log(` Deleted ${messages.length} messages locally`);
|
|
2344
2325
|
// Queue IMAP actions
|
|
@@ -2391,7 +2372,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2391
2372
|
const trash = this.findFolder(accountId, "trash");
|
|
2392
2373
|
// Local first — remove from DB immediately
|
|
2393
2374
|
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2394
|
-
this.db.deleteMessage(accountId, uid);
|
|
2375
|
+
this.db.deleteMessage(accountId, uid, "user-initiated trash", "mailx-imap trashMessage");
|
|
2395
2376
|
// Queue IMAP action + log the resolution so "I deleted a message and
|
|
2396
2377
|
// now it's in neither trash nor deleted" is diagnosable from the log.
|
|
2397
2378
|
if (trash && trash.id !== folderId) {
|
|
@@ -2433,7 +2414,7 @@ export class ImapManager extends EventEmitter {
|
|
|
2433
2414
|
if (!msg)
|
|
2434
2415
|
throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
|
|
2435
2416
|
await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
|
|
2436
|
-
this.db.deleteMessage(fromAccountId, uid);
|
|
2417
|
+
this.db.deleteMessage(fromAccountId, uid, `cross-account move to ${toAccountId}/${toFolder.path}`, "mailx-imap moveBetweenAccounts");
|
|
2437
2418
|
console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
|
|
2438
2419
|
});
|
|
2439
2420
|
});
|
|
@@ -2616,67 +2597,10 @@ export class ImapManager extends EventEmitter {
|
|
|
2616
2597
|
return folders.find(f => f.specialUse === specialUse ||
|
|
2617
2598
|
f.path.toLowerCase() === specialUse.toLowerCase()) || null;
|
|
2618
2599
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
*
|
|
2624
|
-
* Uses a synthetic negative UID so it can't collide with a real APPENDUID
|
|
2625
|
-
* (which is always positive). When the real sync eventually picks the
|
|
2626
|
-
* message up in Sent with the server's UID, `db.upsertMessage` spots
|
|
2627
|
-
* the Message-ID match and rebinds the existing row's UID — no duplicate.
|
|
2628
|
-
* Negative UID also makes the row render pink (getMessages flags uid<0
|
|
2629
|
-
* as pending) so the user sees it's not-yet-reconciled.
|
|
2630
|
-
*
|
|
2631
|
-
* Best-effort — any failure path (no Sent folder yet, parse error, store
|
|
2632
|
-
* write error) is logged and swallowed; the send itself is unaffected. */
|
|
2633
|
-
async insertOptimisticSentRow(accountId, envelope, rawMessage) {
|
|
2634
|
-
try {
|
|
2635
|
-
const sent = this.findFolder(accountId, "sent");
|
|
2636
|
-
if (!sent) {
|
|
2637
|
-
console.log(` [sent-local] no Sent folder for ${accountId}; skipping optimistic row`);
|
|
2638
|
-
return;
|
|
2639
|
-
}
|
|
2640
|
-
// Synthetic UID — negative ms timestamp is monotonic + won't
|
|
2641
|
-
// collide with server UIDs. When the real APPENDUID returns via
|
|
2642
|
-
// sync, upsertMessage's Message-ID rebind swaps this for the
|
|
2643
|
-
// real positive value.
|
|
2644
|
-
const synthUid = -Date.now();
|
|
2645
|
-
const bodyPath = await this.bodyStore.putMessage(accountId, sent.id, synthUid, Buffer.from(rawMessage, "utf-8"));
|
|
2646
|
-
const parsed = await extractPreview(rawMessage);
|
|
2647
|
-
this.db.upsertMessage({
|
|
2648
|
-
accountId,
|
|
2649
|
-
folderId: sent.id,
|
|
2650
|
-
uid: synthUid,
|
|
2651
|
-
messageId: envelope.messageId,
|
|
2652
|
-
inReplyTo: envelope.inReplyTo,
|
|
2653
|
-
references: envelope.references,
|
|
2654
|
-
date: envelope.date,
|
|
2655
|
-
subject: envelope.subject,
|
|
2656
|
-
from: envelope.from,
|
|
2657
|
-
to: envelope.to,
|
|
2658
|
-
cc: envelope.cc,
|
|
2659
|
-
flags: ["\\Seen"],
|
|
2660
|
-
size: rawMessage.length,
|
|
2661
|
-
hasAttachments: parsed.hasAttachments,
|
|
2662
|
-
preview: parsed.preview,
|
|
2663
|
-
bodyPath,
|
|
2664
|
-
});
|
|
2665
|
-
// Folder-tree badge refresh + message-list reload if the user
|
|
2666
|
-
// is currently on Sent — same event shape the sync path emits.
|
|
2667
|
-
// (Was sending {accountId,folderId} as a single arg, which the
|
|
2668
|
-
// IPC forwarder + UI handler decoded as garbage — the optimistic
|
|
2669
|
-
// row landed in the DB but never appeared in the list.)
|
|
2670
|
-
this.db.recalcFolderCounts(sent.id);
|
|
2671
|
-
this.emit("folderCountsChanged", accountId, {});
|
|
2672
|
-
console.log(` [sent-local] wrote optimistic row in Sent (uid=${synthUid}) for ${accountId}: ${envelope.subject}`);
|
|
2673
|
-
}
|
|
2674
|
-
catch (e) {
|
|
2675
|
-
// Non-fatal — send continues, Sent folder just won't show the
|
|
2676
|
-
// row until the real APPEND-then-sync cycle completes.
|
|
2677
|
-
console.error(` [sent-local] optimistic insert failed: ${e?.message || e}`);
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2600
|
+
// insertOptimisticSentRow removed — the synthetic-negative-UID hack
|
|
2601
|
+
// wedged Sent's high-water-mark sync (a synthetic value polluted MAX(uid)
|
|
2602
|
+
// and made every Sent fetch take a stale code path). Sent is now what
|
|
2603
|
+
// the server has, period. Pending sends live in the Outbox view.
|
|
2680
2604
|
/** Copy sent message to the Sent folder via IMAP APPEND */
|
|
2681
2605
|
async copyToSent(accountId, rawMessage) {
|
|
2682
2606
|
const sent = this.findFolder(accountId, "sent");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
13
13
|
"@bobfrankston/mailx-settings": "^0.1.13",
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
15
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.11",
|
|
15
|
+
"@bobfrankston/iflow-direct": "^0.1.32",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.5",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.5",
|
|
18
18
|
"@bobfrankston/mailx-sync": "^0.1.15",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
41
41
|
"@bobfrankston/mailx-settings": "^0.1.13",
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
43
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.11",
|
|
43
|
+
"@bobfrankston/iflow-direct": "^0.1.32",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.5",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.5",
|
|
46
46
|
"@bobfrankston/mailx-sync": "^0.1.15",
|