← back to Claude Mail
gate: add RFC 3834 mail-loop breakers (auto-submitted/bulk/list/no-reply/own-address) before allowlist
f2f1dc0b5b04b7ba6f5ed7774023b3a561430572 · 2026-06-25 10:17:05 -0700 · Steve
Files touched
Diff
commit f2f1dc0b5b04b7ba6f5ed7774023b3a561430572
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jun 25 10:17:05 2026 -0700
gate: add RFC 3834 mail-loop breakers (auto-submitted/bulk/list/no-reply/own-address) before allowlist
---
lib.js | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/lib.js b/lib.js
index 7376cc0..80ef6a0 100644
--- a/lib.js
+++ b/lib.js
@@ -33,6 +33,32 @@ const cfg = {
function gate(parsed) {
const from = (parsed.from?.value?.[0]?.address || '').toLowerCase();
if (!from) return { ok: false, reason: 'no From address' };
+
+ // Mail-loop breakers (RFC 3834) — these run BEFORE the allowlist so an
+ // auto-responder, bounce, or our own outbound copy can never start a
+ // ping-pong even if its From were somehow allowlisted. Purely additive:
+ // legitimate human mail carries none of these headers.
+ if (cfg.user && from === cfg.user.toLowerCase()) {
+ return { ok: false, reason: 'own-address mail (loop guard)' };
+ }
+ const autoSubmitted = headerStr(parsed, 'auto-submitted').toLowerCase();
+ if (autoSubmitted && autoSubmitted !== 'no') {
+ return { ok: false, reason: `auto-submitted mail (loop guard): ${autoSubmitted}` };
+ }
+ const precedence = headerStr(parsed, 'precedence').toLowerCase();
+ if (/\b(bulk|list|junk|auto_reply)\b/.test(precedence)) {
+ return { ok: false, reason: `bulk/auto precedence (loop guard): ${precedence}` };
+ }
+ // Vacation/auto-reply and list-management headers (Gmail/Outlook/Mailman).
+ for (const h of ['x-autoreply', 'x-autorespond', 'x-auto-response-suppress',
+ 'list-id', 'list-unsubscribe', 'feedback-id']) {
+ if (headerStr(parsed, h)) return { ok: false, reason: `auto/list header (loop guard): ${h}` };
+ }
+ const localFrom = from.split('@')[0];
+ if (/^(mailer-daemon|postmaster|no-?reply|do-?not-?reply|bounce)/.test(localFrom)) {
+ return { ok: false, reason: `system/no-reply sender (loop guard): ${from}` };
+ }
+
if (!cfg.allowed.includes(from)) return { ok: false, reason: `sender not allowlisted: ${from}` };
const subject = (parsed.subject || '');
← 2afc049 mailer: archive every reply to IMAP Sent (best-effort) + rea
·
back to Claude Mail
·
poller: guard against invoking claude on an empty prompt (em 1a8facd →