feat(headers): add parsing for Stalwart spam headers

Example of headers created by Stalwart below

X-Spam-Result: TRUSTED_DOMAIN (-7.00),
	PROB_HAM_LOW (-2.00),
	RBL_SENDERSCORE_REPUT_9 (-1.00),
	DMARC_POLICY_ALLOW (-0.50),
	RCVD_DKIM_ARC_DNSWL_MED (-0.50)
X-Spam-Score: ham, score=-5.60, avg_confidence=0.26
X-Spam-Status: No

Only need to add small tweak by detecting spam|ham as well as Yes|No
The most useful header is X-Spam-Score, so we make sure to parse that
before X-Spam-Status
This commit is contained in:
Harry Youd
2026-07-04 14:50:07 +02:00
committed by Linus Rath
parent f291480565
commit 717b1d8397
2 changed files with 4 additions and 3 deletions
+2 -1
View File
@@ -118,7 +118,8 @@ export function parseAuthenticationResults(header: string): AuthenticationResult
*/
export function parseSpamScore(header: string): { score: number; status: string } | null {
// Try X-Spam-Status format: "No, score=-0.25"
const statusMatch = header.match(/^(Yes|No),?\s+score=([-\d.]+)/i);
// And try X-Spam-Score format (Stalwart): "ham, score=-0.25"
const statusMatch = header.match(/^(Yes|No|spam|ham),?\s+score=([-\d.]+)/i);
if (statusMatch) {
return {
status: statusMatch[1].toLowerCase(),
+2 -2
View File
@@ -1243,10 +1243,10 @@ export class JMAPClient implements IJMAPClient {
email.authenticationResults = parseAuthenticationResults(value);
}
for (const headerName of ['X-Spam-Status', 'X-Spam-Result', 'X-Rspamd-Score']) {
for (const headerName of ['X-Spam-Score', 'X-Spam-Status', 'X-Spam-Result', 'X-Rspamd-Score']) {
if (!headersRecord[headerName]) continue;
const value = Array.isArray(headersRecord[headerName]) ? headersRecord[headerName][0] : headersRecord[headerName];
const spamResult = parseSpamScore(value as string);
const spamResult = parseSpamScore((value as string).trim());
if (spamResult) {
email.spamScore = spamResult.score;
email.spamStatus = spamResult.status;