← back to Claude Mail
mailer: archive every reply to IMAP Sent (best-effort) + read-only thread-dump tool
2afc0490759b4904e48dbce35300e8d695e03bcf · 2026-06-25 10:08:28 -0700 · Steve
Files touched
Diff
commit 2afc0490759b4904e48dbce35300e8d695e03bcf
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jun 25 10:08:28 2026 -0700
mailer: archive every reply to IMAP Sent (best-effort) + read-only thread-dump tool
---
lib.js | 34 +++++++++++++++++++++++++---------
thread-dump.js | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 9 deletions(-)
diff --git a/lib.js b/lib.js
index 6aaa324..7376cc0 100644
--- a/lib.js
+++ b/lib.js
@@ -4,6 +4,8 @@
const { spawn } = require('node:child_process');
const nodemailer = require('nodemailer');
+const MailComposer = require('nodemailer/lib/mail-composer');
+const { ImapFlow } = require('imapflow');
const cfg = {
user: process.env.MAIL_USER,
@@ -123,17 +125,31 @@ function transport() {
}
async function sendMail({ to, subject, text, inReplyTo, references }) {
- const t = transport();
- const headers = {};
- const info = await t.sendMail({
+ // Build the raw message ONCE so the SMTP-sent bytes and the archived copy match.
+ const mail = new MailComposer({
from: `"${cfg.fromName}" <${cfg.user}>`,
- to,
- subject,
- text,
- inReplyTo,
- references,
- headers,
+ to, subject, text, inReplyTo, references,
});
+ const raw = await mail.compile().build();
+
+ const info = await transport().sendMail({
+ envelope: { from: cfg.user, to },
+ raw,
+ });
+
+ // Archive a copy to the IMAP Sent folder so every reply is auditable.
+ // Best-effort: a failed append must never fail the send.
+ try {
+ const c = new ImapFlow({
+ host: cfg.imapHost, port: cfg.imapPort, secure: true,
+ auth: { user: cfg.user, pass: cfg.pass }, logger: false,
+ });
+ await c.connect();
+ await c.append('Sent', raw, ['\\Seen']);
+ await c.logout();
+ } catch (e) {
+ console.error(new Date().toISOString(), `sent-archive append failed: ${e.message}`);
+ }
return info;
}
diff --git a/thread-dump.js b/thread-dump.js
new file mode 100644
index 0000000..abe6dd4
--- /dev/null
+++ b/thread-dump.js
@@ -0,0 +1,36 @@
+'use strict';
+// READ-ONLY: dump recent INBOX messages + list folders (to find Sent). No writes.
+const { ImapFlow } = require('imapflow');
+const { simpleParser } = require('mailparser');
+const { cfg } = require('./lib');
+
+(async () => {
+ const client = new ImapFlow({
+ host: cfg.imapHost, port: cfg.imapPort, secure: true,
+ auth: { user: cfg.user, pass: cfg.pass }, logger: false,
+ });
+ await client.connect();
+ console.log('=== mailboxes ===');
+ const boxes = await client.list();
+ for (const mb of boxes) console.log(' ', mb.path);
+
+ const dump = async (box) => {
+ let lock;
+ try { lock = await client.getMailboxLock(box); } catch { console.log(`\n(no ${box})`); return; }
+ try {
+ console.log(`\n=== ${box} (most recent 6) ===`);
+ const all = await client.search({ all: true });
+ const last = (all || []).slice(-6);
+ for (const uid of last) {
+ const { content } = await client.download(uid, undefined, { uid: true });
+ const p = await simpleParser(content);
+ const body = (p.text || '').replace(/\s+\n/g, '\n').trim().slice(0, 600);
+ console.log(`\n--- uid ${uid} | from=${p.from?.text} | to=${p.to?.text}\n subj=${p.subject}\n${body}`);
+ }
+ } finally { lock.release(); }
+ };
+
+ await dump('INBOX');
+ for (const s of ['Sent', 'INBOX.Sent', 'Sent Items']) { await dump(s); }
+ await client.logout().catch(() => {});
+})().catch((e) => { console.error('DUMP FAILED:', e.message); process.exit(1); });
← 92f1320 docs: agentabrams.com Cloudflare migration package (DTD verd
·
back to Claude Mail
·
gate: add RFC 3834 mail-loop breakers (auto-submitted/bulk/l f2f1dc0 →