fix: auto-reply not triggering when source_prefix is empty (#880)

* fix: auto-reply not triggering when source_prefix is empty (#459)

- Empty source_prefix now matches all senders (was short-circuiting as falsy)
- Support regex matching with /pattern/ syntax in source_prefix
- Backward compatible: plain strings still use startsWith
- Use E2E_TEST_MODE switch to skip cloudflare:email import in tests
- Track reply() calls in E2E mock for testability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: update auto-reply UI labels for regex support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update changelogs for auto-reply fix and regex feature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: upgrade version to v1.5.0

- Update version number to 1.5.0 in all package.json files and constants.ts
- Split CHANGELOG: v1.4.0 entries finalized, new v1.5.0(main) section with auto-reply changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add error logging for invalid regex in auto-reply source_prefix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: address CodeRabbit review suggestions

- Use const object instead of let for mock state tracking
- Add log when auto-reply subject/message falls back to defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add source_prefix regex syntax to auto-reply docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dream Hunter
2026-03-08 19:08:06 +08:00
committed by GitHub
parent 10873e7887
commit 5f3762ef58
13 changed files with 272 additions and 26 deletions

View File

@@ -1,6 +1,26 @@
import { createMimeMessage } from "mimetext";
import { getBooleanValue } from "../utils";
/**
* Check if the sender matches the source_prefix filter.
* - empty/undefined: match all senders
* - starts and ends with `/`: treat as regex (e.g. `/.*@example\.com$/`)
* - otherwise: legacy startsWith match
*/
function matchSender(from: string, sourcePrefix: string | undefined): boolean {
if (!sourcePrefix) return true;
if (sourcePrefix.startsWith("/") && sourcePrefix.endsWith("/") && sourcePrefix.length > 2) {
try {
const regex = new RegExp(sourcePrefix.slice(1, -1));
return regex.test(from);
} catch (error) {
console.error("Invalid regex in source_prefix:", sourcePrefix, error);
return false;
}
}
return from.startsWith(sourcePrefix);
}
export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings): Promise<void> => {
const message_id = message.headers.get("Message-ID");
// auto reply email
@@ -9,7 +29,10 @@ export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings
const results = await env.DB.prepare(
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
).bind(message.to).first<Record<string, string>>();
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
if (results && matchSender(message.from, results.source_prefix)) {
if (!results.subject || !results.message) {
console.log("auto-reply using defaults:", !results.subject ? "subject" : "", !results.message ? "message" : "");
}
const msg = createMimeMessage();
msg.setHeader("In-Reply-To", message_id);
msg.setSender({
@@ -22,14 +45,18 @@ export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings
contentType: 'text/plain',
data: results.message || "This is an auto-reply message, please reconact later."
});
const { EmailMessage } = await import('cloudflare:email');
const replyMessage = new EmailMessage(
message.to,
message.from,
msg.asRaw()
);
// @ts-ignore
await message.reply(replyMessage);
if (getBooleanValue(env.E2E_TEST_MODE)) {
await message.reply(msg.asRaw());
} else {
const { EmailMessage } = await import('cloudflare:email');
const replyMessage = new EmailMessage(
message.to,
message.from,
msg.asRaw()
);
// @ts-ignore
await message.reply(replyMessage);
}
}
} catch (error) {
console.log("reply email error", error);