Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ffe294643c | |||
| f1071faf3d | |||
| 6b082cee8f |
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-10 - 2.0.1 - fix(docs/readme)
|
||||||
|
update README: clarify APIs, document RustSecurityBridge, update examples and architecture diagram
|
||||||
|
|
||||||
|
- Documented RustSecurityBridge: startup/shutdown, automatic delegation, compound verifyEmail API, and individual operations
|
||||||
|
- Clarified verification APIs: SpfVerifier.verify() and DmarcVerifier.verify() examples now take an Email object as the first argument
|
||||||
|
- Updated example method names/usages: scanEmail, createEmail, evaluateRoutes, checkMessageLimit, isEmailSuppressed, DKIMCreator rotation and output formatting
|
||||||
|
- Reformatted architecture diagram and added Rust Security Bridge and expanded Rust Acceleration details
|
||||||
|
- Rate limiter example updated: renamed/standardized config keys (maxMessagesPerMinute, domains) and added additional limits (maxRecipientsPerMessage, maxConnectionsPerIP, etc.)
|
||||||
|
- DNS management documentation reorganized: UnifiedEmailServer now handles DNS record setup automatically; DNSManager usage clarified for standalone checks
|
||||||
|
- Minor wording/formatting tweaks throughout README (arrow styles, headings, test counts)
|
||||||
|
|
||||||
## 2026-02-10 - 2.0.0 - BREAKING CHANGE(smartmta)
|
## 2026-02-10 - 2.0.0 - BREAKING CHANGE(smartmta)
|
||||||
Rebrand package to @push.rocks/smartmta, add consolidated email security verification and IPC handler
|
Rebrand package to @push.rocks/smartmta, add consolidated email security verification and IPC handler
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartmta',
|
name: '@push.rocks/smartmta',
|
||||||
version: '1.3.1',
|
version: '2.0.0',
|
||||||
description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
|
description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
|
||||||
};
|
};
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxvQkFBb0I7SUFDMUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHNHQUFzRztDQUNwSCxDQUFBIn0=
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlIQUF5SDtDQUN2SSxDQUFBIn0=
|
||||||
@@ -134,6 +134,7 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|||||||
private servers;
|
private servers;
|
||||||
private stats;
|
private stats;
|
||||||
dkimCreator: DKIMCreator;
|
dkimCreator: DKIMCreator;
|
||||||
|
private rustBridge;
|
||||||
private ipReputationChecker;
|
private ipReputationChecker;
|
||||||
private bounceManager;
|
private bounceManager;
|
||||||
private ipWarmupManager;
|
private ipWarmupManager;
|
||||||
@@ -163,6 +164,11 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
|||||||
* Stop the unified email server
|
* Stop the unified email server
|
||||||
*/
|
*/
|
||||||
stop(): Promise<void>;
|
stop(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Verify inbound email security (DKIM/SPF/DMARC) using the Rust bridge.
|
||||||
|
* Falls back gracefully if the bridge is not running.
|
||||||
|
*/
|
||||||
|
private verifyInboundSecurity;
|
||||||
/**
|
/**
|
||||||
* Process email based on routing rules
|
* Process email based on routing rules
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
3
dist_ts/plugins.d.ts
vendored
3
dist_ts/plugins.d.ts
vendored
@@ -32,10 +32,11 @@ import * as smartproxy from '@push.rocks/smartproxy';
|
|||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as smartrequest from '@push.rocks/smartrequest';
|
import * as smartrequest from '@push.rocks/smartrequest';
|
||||||
import * as smartrule from '@push.rocks/smartrule';
|
import * as smartrule from '@push.rocks/smartrule';
|
||||||
|
import * as smartrust from '@push.rocks/smartrust';
|
||||||
import * as smartrx from '@push.rocks/smartrx';
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
export declare const smartfs: SmartFs;
|
export declare const smartfs: SmartFs;
|
||||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrx, smartunique };
|
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrust, smartrx, smartunique };
|
||||||
export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
|
export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
export { cloudflare, };
|
export { cloudflare, };
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ import * as smartproxy from '@push.rocks/smartproxy';
|
|||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as smartrequest from '@push.rocks/smartrequest';
|
import * as smartrequest from '@push.rocks/smartrequest';
|
||||||
import * as smartrule from '@push.rocks/smartrule';
|
import * as smartrule from '@push.rocks/smartrule';
|
||||||
|
import * as smartrust from '@push.rocks/smartrust';
|
||||||
import * as smartrx from '@push.rocks/smartrx';
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrx, smartunique };
|
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrust, smartrx, smartunique };
|
||||||
// apiclient.xyz scope
|
// apiclient.xyz scope
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
export { cloudflare, };
|
export { cloudflare, };
|
||||||
@@ -53,4 +54,4 @@ import mailparser from 'mailparser';
|
|||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import * as ip from 'ip';
|
import * as ip from 'ip';
|
||||||
export { mailauth, dkimSign, mailparser, uuid, ip, };
|
export { mailauth, dkimSign, mailparser, uuid, ip, };
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFDTCxHQUFHLEVBQ0gsRUFBRSxFQUNGLE1BQU0sRUFDTixJQUFJLEVBQ0osR0FBRyxFQUNILEVBQUUsRUFDRixJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxtQkFBbUIsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxPQUFPLEVBQ0wsbUJBQW1CLEVBQ3BCLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFlBQVksRUFDWixXQUFXLEVBQ1gsV0FBVyxHQUNaLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25FLE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBQy9DLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsTUFBTSxDQUFDLE1BQU0sT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLElBQUksbUJBQW1CLEVBQUUsQ0FBQyxDQUFDO0FBRTlELE9BQU8sRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFLNU8sc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUNMLFVBQVUsR0FDWCxDQUFBO0FBRUQsZ0JBQWdCO0FBQ2hCLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUNMLE9BQU8sR0FDUixDQUFBO0FBRUQsY0FBYztBQUNkLE9BQU8sS0FBSyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLFVBQVUsTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFekIsT0FBTyxFQUNMLFFBQVEsRUFDUixRQUFRLEVBQ1IsVUFBVSxFQUNWLElBQUksRUFDSixFQUFFLEdBQ0gsQ0FBQSJ9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFDTCxHQUFHLEVBQ0gsRUFBRSxFQUNGLE1BQU0sRUFDTixJQUFJLEVBQ0osR0FBRyxFQUNILEVBQUUsRUFDRixJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxtQkFBbUIsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxPQUFPLEVBQ0wsbUJBQW1CLEVBQ3BCLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFlBQVksRUFDWixXQUFXLEVBQ1gsV0FBVyxHQUNaLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25FLE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUV2RCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsSUFBSSxtQkFBbUIsRUFBRSxDQUFDLENBQUM7QUFFOUQsT0FBTyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFLdlAsc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUNMLFVBQVUsR0FDWCxDQUFBO0FBRUQsZ0JBQWdCO0FBQ2hCLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUNMLE9BQU8sR0FDUixDQUFBO0FBRUQsY0FBYztBQUNkLE9BQU8sS0FBSyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLFVBQVUsTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFekIsT0FBTyxFQUNMLFFBQVEsRUFDUixRQUFRLEVBQ1IsVUFBVSxFQUNWLElBQUksRUFDSixFQUFFLEdBQ0gsQ0FBQSJ9
|
||||||
File diff suppressed because one or more lines are too long
126
dist_ts/security/classes.rustsecuritybridge.d.ts
vendored
Normal file
126
dist_ts/security/classes.rustsecuritybridge.d.ts
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
interface IDkimVerificationResult {
|
||||||
|
is_valid: boolean;
|
||||||
|
domain: string | null;
|
||||||
|
selector: string | null;
|
||||||
|
status: string;
|
||||||
|
details: string | null;
|
||||||
|
}
|
||||||
|
interface ISpfResult {
|
||||||
|
result: string;
|
||||||
|
domain: string;
|
||||||
|
ip: string;
|
||||||
|
explanation: string | null;
|
||||||
|
}
|
||||||
|
interface IDmarcResult {
|
||||||
|
passed: boolean;
|
||||||
|
policy: string;
|
||||||
|
domain: string;
|
||||||
|
dkim_result: string;
|
||||||
|
spf_result: string;
|
||||||
|
action: string;
|
||||||
|
details: string | null;
|
||||||
|
}
|
||||||
|
interface IEmailSecurityResult {
|
||||||
|
dkim: IDkimVerificationResult[];
|
||||||
|
spf: ISpfResult | null;
|
||||||
|
dmarc: IDmarcResult | null;
|
||||||
|
}
|
||||||
|
interface IValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
formatValid: boolean;
|
||||||
|
score: number;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
interface IBounceDetection {
|
||||||
|
bounce_type: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
interface IReputationResult {
|
||||||
|
ip: string;
|
||||||
|
score: number;
|
||||||
|
risk_level: string;
|
||||||
|
ip_type: string;
|
||||||
|
dnsbl_results: Array<{
|
||||||
|
server: string;
|
||||||
|
listed: boolean;
|
||||||
|
response: string | null;
|
||||||
|
}>;
|
||||||
|
listed_count: number;
|
||||||
|
total_checked: number;
|
||||||
|
}
|
||||||
|
interface IVersionInfo {
|
||||||
|
bin: string;
|
||||||
|
core: string;
|
||||||
|
security: string;
|
||||||
|
smtp: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Bridge between TypeScript and the Rust `mailer-bin` binary.
|
||||||
|
*
|
||||||
|
* Uses `@push.rocks/smartrust` for JSON-over-stdin/stdout IPC.
|
||||||
|
* Singleton — access via `RustSecurityBridge.getInstance()`.
|
||||||
|
*/
|
||||||
|
export declare class RustSecurityBridge {
|
||||||
|
private static instance;
|
||||||
|
private bridge;
|
||||||
|
private _running;
|
||||||
|
private constructor();
|
||||||
|
/** Get or create the singleton instance. */
|
||||||
|
static getInstance(): RustSecurityBridge;
|
||||||
|
/** Whether the Rust process is currently running and accepting commands. */
|
||||||
|
get running(): boolean;
|
||||||
|
/**
|
||||||
|
* Spawn the Rust binary and wait for the ready signal.
|
||||||
|
* @returns `true` if the binary started successfully, `false` otherwise.
|
||||||
|
*/
|
||||||
|
start(): Promise<boolean>;
|
||||||
|
/** Kill the Rust process. */
|
||||||
|
stop(): Promise<void>;
|
||||||
|
/** Ping the Rust process. */
|
||||||
|
ping(): Promise<boolean>;
|
||||||
|
/** Get version information for all Rust crates. */
|
||||||
|
getVersion(): Promise<IVersionInfo>;
|
||||||
|
/** Validate an email address. */
|
||||||
|
validateEmail(email: string): Promise<IValidationResult>;
|
||||||
|
/** Detect bounce type from SMTP response / diagnostic code. */
|
||||||
|
detectBounce(opts: {
|
||||||
|
smtpResponse?: string;
|
||||||
|
diagnosticCode?: string;
|
||||||
|
statusCode?: string;
|
||||||
|
}): Promise<IBounceDetection>;
|
||||||
|
/** Check IP reputation via DNSBL. */
|
||||||
|
checkIpReputation(ip: string): Promise<IReputationResult>;
|
||||||
|
/** Verify DKIM signatures on a raw email message. */
|
||||||
|
verifyDkim(rawMessage: string): Promise<IDkimVerificationResult[]>;
|
||||||
|
/** Sign an email with DKIM. */
|
||||||
|
signDkim(opts: {
|
||||||
|
rawMessage: string;
|
||||||
|
domain: string;
|
||||||
|
selector?: string;
|
||||||
|
privateKey: string;
|
||||||
|
}): Promise<{
|
||||||
|
header: string;
|
||||||
|
signedMessage: string;
|
||||||
|
}>;
|
||||||
|
/** Check SPF for a sender. */
|
||||||
|
checkSpf(opts: {
|
||||||
|
ip: string;
|
||||||
|
heloDomain: string;
|
||||||
|
hostname?: string;
|
||||||
|
mailFrom: string;
|
||||||
|
}): Promise<ISpfResult>;
|
||||||
|
/**
|
||||||
|
* Compound email security verification: DKIM + SPF + DMARC in one IPC call.
|
||||||
|
*
|
||||||
|
* This is the preferred method for inbound email verification — it avoids
|
||||||
|
* 3 sequential round-trips and correctly passes raw mail-auth types internally.
|
||||||
|
*/
|
||||||
|
verifyEmail(opts: {
|
||||||
|
rawMessage: string;
|
||||||
|
ip: string;
|
||||||
|
heloDomain: string;
|
||||||
|
hostname?: string;
|
||||||
|
mailFrom: string;
|
||||||
|
}): Promise<IEmailSecurityResult>;
|
||||||
|
}
|
||||||
|
export type { IDkimVerificationResult, ISpfResult, IDmarcResult, IEmailSecurityResult, IValidationResult, IBounceDetection, IReputationResult as IRustReputationResult, IVersionInfo, };
|
||||||
141
dist_ts/security/classes.rustsecuritybridge.js
Normal file
141
dist_ts/security/classes.rustsecuritybridge.js
Normal file
File diff suppressed because one or more lines are too long
1
dist_ts/security/index.d.ts
vendored
1
dist_ts/security/index.d.ts
vendored
@@ -1,3 +1,4 @@
|
|||||||
export { SecurityLogger, SecurityLogLevel, SecurityEventType, type ISecurityEvent } from './classes.securitylogger.js';
|
export { SecurityLogger, SecurityLogLevel, SecurityEventType, type ISecurityEvent } from './classes.securitylogger.js';
|
||||||
export { IPReputationChecker, ReputationThreshold, IPType, type IReputationResult, type IIPReputationOptions } from './classes.ipreputationchecker.js';
|
export { IPReputationChecker, ReputationThreshold, IPType, type IReputationResult, type IIPReputationOptions } from './classes.ipreputationchecker.js';
|
||||||
export { ContentScanner, ThreatCategory, type IScanResult, type IContentScannerOptions } from './classes.contentscanner.js';
|
export { ContentScanner, ThreatCategory, type IScanResult, type IContentScannerOptions } from './classes.contentscanner.js';
|
||||||
|
export { RustSecurityBridge, type IDkimVerificationResult, type ISpfResult, type IDmarcResult, type IEmailSecurityResult, type IValidationResult, type IBounceDetection, type IRustReputationResult, type IVersionInfo, } from './classes.rustsecuritybridge.js';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
export { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
||||||
export { IPReputationChecker, ReputationThreshold, IPType } from './classes.ipreputationchecker.js';
|
export { IPReputationChecker, ReputationThreshold, IPType } from './classes.ipreputationchecker.js';
|
||||||
export { ContentScanner, ThreatCategory } from './classes.contentscanner.js';
|
export { ContentScanner, ThreatCategory } from './classes.contentscanner.js';
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDIn0=
|
export { RustSecurityBridge, } from './classes.rustsecuritybridge.js';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDO0FBRXJDLE9BQU8sRUFDTCxrQkFBa0IsR0FTbkIsTUFBTSxpQ0FBaUMsQ0FBQyJ9
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartmta",
|
"name": "@push.rocks/smartmta",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.",
|
"description": "A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"mta",
|
"mta",
|
||||||
|
|||||||
259
readme.md
259
readme.md
@@ -1,6 +1,6 @@
|
|||||||
# @push.rocks/smartmta
|
# @push.rocks/smartmta
|
||||||
|
|
||||||
A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration — no nodemailer, no shortcuts. 🚀
|
A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration — no nodemailer, no shortcuts.
|
||||||
|
|
||||||
## Issue Reporting and Security
|
## Issue Reporting and Security
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ npm install @push.rocks/smartmta
|
|||||||
|
|
||||||
`@push.rocks/smartmta` is a **complete mail server solution** — SMTP server, SMTP client, email security, content scanning, and delivery management — all built with a custom SMTP implementation. No wrappers around nodemailer. No half-measures.
|
`@push.rocks/smartmta` is a **complete mail server solution** — SMTP server, SMTP client, email security, content scanning, and delivery management — all built with a custom SMTP implementation. No wrappers around nodemailer. No half-measures.
|
||||||
|
|
||||||
### ✨ What's Inside
|
### What's Inside
|
||||||
|
|
||||||
| Module | What It Does |
|
| Module | What It Does |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -31,38 +31,42 @@ npm install @push.rocks/smartmta
|
|||||||
| **Bounce Manager** | Automatic bounce detection, classification (hard/soft), and tracking |
|
| **Bounce Manager** | Automatic bounce detection, classification (hard/soft), and tracking |
|
||||||
| **Content Scanner** | Spam, phishing, malware, XSS, and suspicious link detection |
|
| **Content Scanner** | Spam, phishing, malware, XSS, and suspicious link detection |
|
||||||
| **IP Reputation** | DNSBL checks, proxy/TOR/VPN detection, risk scoring |
|
| **IP Reputation** | DNSBL checks, proxy/TOR/VPN detection, risk scoring |
|
||||||
| **Rate Limiter** | Hierarchical rate limiting (global, per-domain, per-sender) |
|
| **Rate Limiter** | Hierarchical rate limiting (global, per-domain, per-IP) |
|
||||||
| **Delivery Queue** | Persistent queue with exponential backoff retry |
|
| **Delivery Queue** | Persistent queue with exponential backoff retry |
|
||||||
| **Template Engine** | Email templates with variable substitution |
|
| **Template Engine** | Email templates with variable substitution |
|
||||||
| **Domain Registry** | Multi-domain management with per-domain configuration |
|
| **Domain Registry** | Multi-domain management with per-domain configuration |
|
||||||
| **DNS Manager** | Automatic DNS record management with Cloudflare API integration |
|
| **DNS Manager** | Automatic DNS record management with Cloudflare API integration |
|
||||||
| **Rust Accelerator** | Performance-critical operations (DKIM, MIME, validation) in Rust via IPC |
|
| **Rust Accelerator** | Performance-critical operations (DKIM, MIME, validation) in Rust via IPC |
|
||||||
|
| **Rust Security Bridge** | Compound email security verification (DKIM+SPF+DMARC) via Rust binary |
|
||||||
|
|
||||||
### 🏗️ Architecture
|
### Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────┐
|
||||||
│ UnifiedEmailServer │
|
│ UnifiedEmailServer │
|
||||||
│ (orchestrates all components, emits events) │
|
│ (orchestrates all components, emits events) │
|
||||||
├──────────┬──────────┬───────────┬───────────────────┤
|
├──────────┬──────────┬────────────┬──────────────────────┤
|
||||||
│ SMTP │ Email │ Security │ Delivery │
|
│ SMTP │ Email │ Security │ Delivery │
|
||||||
│ Server │ Router │ Stack │ System │
|
│ Server │ Router │ Stack │ System │
|
||||||
│ ┌─────┐ │ ┌─────┐ │ ┌──────┐ │ ┌─────────────┐ │
|
│ ┌─────┐ │ ┌─────┐ │ ┌───────┐ │ ┌────────────────┐ │
|
||||||
│ │ TLS │ │ │Match│ │ │ DKIM │ │ │ Queue │ │
|
│ │ TLS │ │ │Match│ │ │ DKIM │ │ │ Queue │ │
|
||||||
│ │ Auth│ │ │Route│ │ │ SPF │ │ │ Rate Limit │ │
|
│ │ Auth│ │ │Route│ │ │ SPF │ │ │ Rate Limit │ │
|
||||||
│ │ Cmd │ │ │ Act │ │ │DMARC │ │ │ SMTP Client │ │
|
│ │ Cmd │ │ │ Act │ │ │ DMARC │ │ │ SMTP Client │ │
|
||||||
│ │ Data│ │ │ │ │ │IPRep │ │ │ Retry Logic │ │
|
│ │ Data│ │ │ │ │ │ IPRep │ │ │ Retry Logic │ │
|
||||||
│ └─────┘ │ └─────┘ │ │Scan │ │ └─────────────┘ │
|
│ └─────┘ │ └─────┘ │ │ Scan │ │ └────────────────┘ │
|
||||||
│ │ │ └──────┘ │ │
|
│ │ │ └───────┘ │ │
|
||||||
├──────────┴──────────┴───────────┴───────────────────┤
|
├──────────┴──────────┴────────────┴──────────────────────┤
|
||||||
│ Rust Acceleration Layer │
|
│ Rust Security Bridge │
|
||||||
│ (mailer-core, mailer-security via smartrust IPC) │
|
│ (RustSecurityBridge singleton via smartrust IPC) │
|
||||||
└─────────────────────────────────────────────────────┘
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Rust Acceleration Layer │
|
||||||
|
│ (mailer-core, mailer-security, mailer-bin) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### 🔧 Setting Up the Email Server
|
### Setting Up the Email Server
|
||||||
|
|
||||||
The central entry point is `UnifiedEmailServer`, which orchestrates SMTP, routing, security, and delivery:
|
The central entry point is `UnifiedEmailServer`, which orchestrates SMTP, routing, security, and delivery:
|
||||||
|
|
||||||
@@ -134,7 +138,7 @@ const emailServer = new UnifiedEmailServer(dcRouterRef, {
|
|||||||
await emailServer.start();
|
await emailServer.start();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📧 Sending Emails with the SMTP Client
|
### Sending Emails with the SMTP Client
|
||||||
|
|
||||||
Create and send emails using the built-in SMTP client with connection pooling:
|
Create and send emails using the built-in SMTP client with connection pooling:
|
||||||
|
|
||||||
@@ -177,9 +181,9 @@ const result = await client.sendMail(email);
|
|||||||
console.log(`Message sent: ${result.messageId}`);
|
console.log(`Message sent: ${result.messageId}`);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🔐 DKIM Signing
|
### DKIM Signing
|
||||||
|
|
||||||
Automatic DKIM key generation, storage, and signing per domain:
|
DKIM key management is handled by `DKIMCreator`, which generates, stores, and rotates keys per domain. Signing is performed automatically by `UnifiedEmailServer` during outbound delivery — there is no standalone `signEmail()` call:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { DKIMCreator } from '@push.rocks/smartmta';
|
import { DKIMCreator } from '@push.rocks/smartmta';
|
||||||
@@ -192,36 +196,45 @@ await dkimCreator.handleDKIMKeysForDomain('example.com');
|
|||||||
// Get the DNS record you need to publish
|
// Get the DNS record you need to publish
|
||||||
const dnsRecord = await dkimCreator.getDNSRecordForDomain('example.com');
|
const dnsRecord = await dkimCreator.getDNSRecordForDomain('example.com');
|
||||||
console.log(dnsRecord);
|
console.log(dnsRecord);
|
||||||
// → { type: 'TXT', name: 'default._domainkey.example.com', value: 'v=DKIM1; k=rsa; p=...' }
|
// -> { type: 'TXT', name: 'default._domainkey.example.com', value: 'v=DKIM1; k=rsa; p=...' }
|
||||||
|
|
||||||
// Sign an email
|
// Check if keys need rotation
|
||||||
const signedEmail = await dkimCreator.signEmail(email);
|
const needsRotation = await dkimCreator.needsRotation('example.com', 'default', 90);
|
||||||
|
if (needsRotation) {
|
||||||
|
const newSelector = await dkimCreator.rotateDkimKeys('example.com', 'default', 2048);
|
||||||
|
console.log(`Rotated to selector: ${newSelector}`);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🛡️ Email Authentication (SPF, DKIM, DMARC)
|
When `UnifiedEmailServer.start()` is called, DKIM signing is applied to all outbound mail automatically using the keys managed by `DKIMCreator`. The `RustSecurityBridge` can also perform DKIM signing via its `signDkim()` method for high-performance scenarios.
|
||||||
|
|
||||||
Verify incoming emails against all three authentication standards:
|
### Email Authentication (SPF, DKIM, DMARC)
|
||||||
|
|
||||||
|
Verify incoming emails against all three authentication standards. Note that the first argument to `SpfVerifier.verify()` and `DmarcVerifier.verify()` is an `Email` object:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { DKIMVerifier, SpfVerifier, DmarcVerifier } from '@push.rocks/smartmta';
|
import { DKIMVerifier, SpfVerifier, DmarcVerifier } from '@push.rocks/smartmta';
|
||||||
|
|
||||||
// SPF verification
|
// SPF verification — first arg is an Email object
|
||||||
const spfVerifier = new SpfVerifier();
|
const spfVerifier = new SpfVerifier();
|
||||||
const spfResult = await spfVerifier.verify(senderIP, senderDomain, ehloHostname);
|
const spfResult = await spfVerifier.verify(email, senderIP, heloDomain);
|
||||||
// → { result: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none' | 'temperror' | 'permerror' }
|
// -> { result: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none' | 'temperror' | 'permerror',
|
||||||
|
// domain: string, ip: string }
|
||||||
|
|
||||||
// DKIM verification
|
// DKIM verification
|
||||||
const dkimVerifier = new DKIMVerifier();
|
const dkimVerifier = new DKIMVerifier();
|
||||||
const dkimResult = await dkimVerifier.verify(rawEmailContent);
|
const dkimResult = await dkimVerifier.verify(rawEmailContent);
|
||||||
|
|
||||||
// DMARC verification
|
// DMARC verification — first arg is an Email object
|
||||||
const dmarcVerifier = new DmarcVerifier();
|
const dmarcVerifier = new DmarcVerifier();
|
||||||
const dmarcResult = await dmarcVerifier.verify(fromDomain, spfResult, dkimResult);
|
const dmarcResult = await dmarcVerifier.verify(email, spfResult, dkimResult);
|
||||||
|
// -> { action: 'pass' | 'quarantine' | 'reject', hasDmarc: boolean,
|
||||||
|
// spfDomainAligned: boolean, dkimDomainAligned: boolean, ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🔀 Email Routing
|
### Email Routing
|
||||||
|
|
||||||
Pattern-based routing engine with priority ordering and flexible match criteria:
|
Pattern-based routing engine with priority ordering and flexible match criteria. Routes are evaluated by priority (highest first) using `evaluateRoutes()`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { EmailRouter } from '@push.rocks/smartmta';
|
import { EmailRouter } from '@push.rocks/smartmta';
|
||||||
@@ -271,13 +284,13 @@ const router = new EmailRouter([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Routes are evaluated by priority (highest first)
|
// Evaluate routes against an email context
|
||||||
const matchedRoute = router.route(email, context);
|
const matchedRoute = await router.evaluateRoutes(emailContext);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🕵️ Content Scanning
|
### Content Scanning
|
||||||
|
|
||||||
Built-in content scanner for detecting spam, phishing, malware, and other threats:
|
Built-in content scanner for detecting spam, phishing, malware, and other threats. Use the `scanEmail()` method:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { ContentScanner } from '@push.rocks/smartmta';
|
import { ContentScanner } from '@push.rocks/smartmta';
|
||||||
@@ -300,11 +313,11 @@ const scanner = new ContentScanner({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await scanner.scan(email);
|
const result = await scanner.scanEmail(email);
|
||||||
// → { isClean: false, threatScore: 85, threatType: 'phishing', scannedElements: [...] }
|
// -> { isClean: false, threatScore: 85, threatType: 'phishing', scannedElements: [...] }
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🌐 IP Reputation Checking
|
### IP Reputation Checking
|
||||||
|
|
||||||
Check sender IP addresses against DNSBL blacklists and classify IP types:
|
Check sender IP addresses against DNSBL blacklists and classify IP types:
|
||||||
|
|
||||||
@@ -318,37 +331,52 @@ const ipChecker = new IPReputationChecker({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const reputation = await ipChecker.checkReputation('192.168.1.1');
|
const reputation = await ipChecker.checkReputation('192.168.1.1');
|
||||||
// → { score: 85, isSpam: false, isProxy: false, isTor: false, blacklists: [] }
|
// -> { score: 85, isSpam: false, isProxy: false, isTor: false, blacklists: [] }
|
||||||
```
|
```
|
||||||
|
|
||||||
### ⏱️ Rate Limiting
|
When the `RustSecurityBridge` is running, `IPReputationChecker` automatically delegates DNSBL lookups to the Rust binary for improved performance.
|
||||||
|
|
||||||
Hierarchical rate limiting to protect your server and maintain deliverability:
|
### Rate Limiting
|
||||||
|
|
||||||
|
Hierarchical rate limiting to protect your server and maintain deliverability. Configuration uses `maxMessagesPerMinute` and organizes domain-level limits under the `domains` key:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { UnifiedRateLimiter } from '@push.rocks/smartmta';
|
import { UnifiedRateLimiter } from '@push.rocks/smartmta';
|
||||||
|
|
||||||
const rateLimiter = new UnifiedRateLimiter({
|
const rateLimiter = new UnifiedRateLimiter({
|
||||||
global: {
|
global: {
|
||||||
maxPerMinute: 1000,
|
maxMessagesPerMinute: 1000,
|
||||||
maxPerHour: 10000,
|
maxRecipientsPerMessage: 500,
|
||||||
|
maxConnectionsPerIP: 20,
|
||||||
|
maxErrorsPerIP: 10,
|
||||||
|
maxAuthFailuresPerIP: 5,
|
||||||
|
blockDuration: 600000, // 10 minutes
|
||||||
},
|
},
|
||||||
perDomain: {
|
domains: {
|
||||||
'example.com': {
|
'example.com': {
|
||||||
maxPerMinute: 100,
|
maxMessagesPerMinute: 100,
|
||||||
maxPerHour: 1000,
|
maxRecipientsPerMessage: 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
perSender: {
|
|
||||||
maxPerMinute: 20,
|
|
||||||
maxPerHour: 200,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check before sending
|
||||||
|
const allowed = rateLimiter.checkMessageLimit(
|
||||||
|
'sender@example.com',
|
||||||
|
'192.168.1.1',
|
||||||
|
recipientCount,
|
||||||
|
undefined,
|
||||||
|
'example.com'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowed.allowed) {
|
||||||
|
console.log(`Rate limited: ${allowed.reason}`);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📬 Bounce Management
|
### Bounce Management
|
||||||
|
|
||||||
Automatic bounce detection, classification, and tracking:
|
Automatic bounce detection, classification, and suppression tracking. Use `isEmailSuppressed()` to check if an address should be suppressed:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { BounceManager } from '@push.rocks/smartmta';
|
import { BounceManager } from '@push.rocks/smartmta';
|
||||||
@@ -361,22 +389,26 @@ const bounce = await bounceManager.processSmtpFailure(
|
|||||||
'550 5.1.1 User unknown',
|
'550 5.1.1 User unknown',
|
||||||
{ originalEmailId: 'msg-123' }
|
{ originalEmailId: 'msg-123' }
|
||||||
);
|
);
|
||||||
// → { bounceType: 'invalid_recipient', bounceCategory: 'hard', ... }
|
// -> { bounceType: 'invalid_recipient', bounceCategory: 'hard', ... }
|
||||||
|
|
||||||
// Check if an address is known to bounce
|
// Check if an address is suppressed due to bounces
|
||||||
const shouldSuppress = bounceManager.shouldSuppressDelivery('recipient@example.com');
|
const suppressed = bounceManager.isEmailSuppressed('recipient@example.com');
|
||||||
|
|
||||||
|
// Manually manage the suppression list
|
||||||
|
bounceManager.addToSuppressionList('bad@example.com', 'repeated hard bounces');
|
||||||
|
bounceManager.removeFromSuppressionList('recovered@example.com');
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📝 Email Templates
|
### Email Templates
|
||||||
|
|
||||||
Template engine with variable substitution for transactional and notification emails:
|
Template engine with variable substitution for transactional and notification emails. Use `createEmail()` to produce a ready-to-send `Email` from a registered template:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { TemplateManager } from '@push.rocks/smartmta';
|
import { TemplateManager } from '@push.rocks/smartmta';
|
||||||
|
|
||||||
const templates = new TemplateManager({
|
const templates = new TemplateManager({
|
||||||
from: 'noreply@example.com',
|
from: 'noreply@example.com',
|
||||||
footerHtml: '<p>© 2026 Example Corp</p>',
|
footerHtml: '<p>2026 Example Corp</p>',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register a template
|
// Register a template
|
||||||
@@ -391,52 +423,107 @@ templates.registerTemplate({
|
|||||||
category: 'transactional',
|
category: 'transactional',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render and send
|
// Create an Email object from the template
|
||||||
const email = templates.renderTemplate('welcome', {
|
const email = await templates.createEmail('welcome', {
|
||||||
to: 'newuser@example.com',
|
to: 'newuser@example.com',
|
||||||
variables: { name: 'Alice' },
|
variables: { name: 'Alice' },
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🌍 DNS Management with Cloudflare
|
### DNS Management
|
||||||
|
|
||||||
Automatic DNS record setup for MX, SPF, DKIM, and DMARC via the Cloudflare API:
|
DNS record management for email authentication is handled internally by `UnifiedEmailServer`. The `DnsManager` is not instantiated directly — it receives its configuration from the `dcRouter` reference and automatically ensures MX, SPF, DKIM, and DMARC records are in place for all configured domains:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { DnsManager } from '@push.rocks/smartmta';
|
// DNS management is automatic when using UnifiedEmailServer.
|
||||||
|
// When the server starts, it calls ensureDnsRecords() internally
|
||||||
|
// for all configured domains, setting up:
|
||||||
|
// - MX records pointing to your mail server
|
||||||
|
// - SPF TXT records authorizing your server IP
|
||||||
|
// - DKIM TXT records with public keys from DKIMCreator
|
||||||
|
// - DMARC TXT records with your policy
|
||||||
|
|
||||||
const dnsManager = new DnsManager({
|
const emailServer = new UnifiedEmailServer(dcRouterRef, {
|
||||||
|
hostname: 'mail.example.com',
|
||||||
domains: [
|
domains: [
|
||||||
{
|
{
|
||||||
domain: 'example.com',
|
domain: 'example.com',
|
||||||
dnsMode: 'external-dns', // managed via Cloudflare API
|
dnsMode: 'external-dns', // managed via Cloudflare API
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// ... other config
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-configure all required DNS records
|
// DNS records are set up automatically on start
|
||||||
await dnsManager.setupDnsForDomain('example.com', {
|
await emailServer.start();
|
||||||
serverIp: '203.0.113.10',
|
|
||||||
mxHostname: 'mail.example.com',
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🦀 Rust Acceleration
|
For DNS lookups and record verification outside of the server lifecycle, the `DNSManager` class (note the capital N) can be used directly:
|
||||||
|
|
||||||
Performance-critical operations are implemented in Rust and communicate with the TypeScript runtime via `@push.rocks/smartrust` (JSON-over-stdin/stdout IPC):
|
```typescript
|
||||||
|
import { DNSManager, DKIMCreator } from '@push.rocks/smartmta';
|
||||||
|
|
||||||
- **mailer-core**: Email type validation, MIME building, bounce detection
|
const dkimCreator = new DKIMCreator('/path/to/keys');
|
||||||
- **mailer-security**: DKIM signing/verification, SPF checks, DMARC policy, IP reputation/DNSBL
|
const dnsManager = new DNSManager(dkimCreator);
|
||||||
|
|
||||||
|
// Verify all email authentication records for a domain
|
||||||
|
const results = await dnsManager.verifyEmailAuthRecords('example.com', 'default');
|
||||||
|
console.log(results.spf); // { valid: boolean, record: string, ... }
|
||||||
|
console.log(results.dkim); // { valid: boolean, record: string, ... }
|
||||||
|
console.log(results.dmarc); // { valid: boolean, record: string, ... }
|
||||||
|
|
||||||
|
// Generate recommended DNS records
|
||||||
|
const records = await dnsManager.generateAllRecommendedRecords('example.com');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rust Acceleration
|
||||||
|
|
||||||
|
Performance-critical operations are implemented in Rust and communicate with the TypeScript runtime via `@push.rocks/smartrust` (JSON-over-stdin/stdout IPC).
|
||||||
|
|
||||||
|
### Rust Crates
|
||||||
|
|
||||||
The Rust workspace is at `rust/` with five crates:
|
The Rust workspace is at `rust/` with five crates:
|
||||||
|
|
||||||
| Crate | Status | Purpose |
|
| Crate | Status | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `mailer-core` | ✅ Complete | Email types, validation, MIME, bounce detection |
|
| `mailer-core` | Complete (26 tests) | Email types, validation, MIME building, bounce detection |
|
||||||
| `mailer-security` | ✅ Complete | DKIM, SPF, DMARC, IP reputation |
|
| `mailer-security` | Complete (12 tests) | DKIM signing/verification, SPF checks, DMARC policy, IP reputation/DNSBL |
|
||||||
| `mailer-bin` | ✅ Complete | CLI + smartrust IPC bridge |
|
| `mailer-bin` | Complete | CLI + smartrust IPC bridge (handles `verifyEmail` compound method) |
|
||||||
| `mailer-smtp` | 🔜 Phase 2 | SMTP protocol in Rust |
|
| `mailer-smtp` | Planned (Phase 3) | SMTP protocol in Rust |
|
||||||
| `mailer-napi` | 🔜 Phase 2 | Native Node.js addon |
|
| `mailer-napi` | Planned (Phase 3) | Native Node.js addon |
|
||||||
|
|
||||||
|
### RustSecurityBridge
|
||||||
|
|
||||||
|
The `RustSecurityBridge` is a singleton that manages the Rust binary process and provides high-performance security verification. It is automatically started and stopped with `UnifiedEmailServer`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { RustSecurityBridge } from '@push.rocks/smartmta';
|
||||||
|
|
||||||
|
const bridge = RustSecurityBridge.getInstance();
|
||||||
|
await bridge.start();
|
||||||
|
|
||||||
|
// Compound verification: DKIM + SPF + DMARC in a single IPC call
|
||||||
|
const securityResult = await bridge.verifyEmail({
|
||||||
|
rawMessage: rawEmailString,
|
||||||
|
ip: '203.0.113.10',
|
||||||
|
heloDomain: 'sender.example.com',
|
||||||
|
mailFrom: 'user@example.com',
|
||||||
|
});
|
||||||
|
// -> { dkim: [...], spf: { result, explanation }, dmarc: { result, policy } }
|
||||||
|
|
||||||
|
// Individual operations
|
||||||
|
const dkimResults = await bridge.verifyDkim(rawEmailString);
|
||||||
|
const spfResult = await bridge.checkSpf({
|
||||||
|
ip: '203.0.113.10',
|
||||||
|
heloDomain: 'sender.example.com',
|
||||||
|
mailFrom: 'user@example.com',
|
||||||
|
});
|
||||||
|
const reputationResult = await bridge.checkIpReputation('203.0.113.10');
|
||||||
|
|
||||||
|
await bridge.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
When the bridge is running, the TypeScript security components (`SpfVerifier`, `DKIMVerifier`, `IPReputationChecker`) automatically delegate to the Rust binary. If the binary is unavailable, the system falls back gracefully to TypeScript-only verification.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -450,10 +537,10 @@ smartmta/
|
|||||||
│ │ │ └── smtpserver/ # SMTP server with TLS, auth, pipelining
|
│ │ │ └── smtpserver/ # SMTP server with TLS, auth, pipelining
|
||||||
│ │ ├── routing/ # UnifiedEmailServer, EmailRouter, DomainRegistry, DnsManager
|
│ │ ├── routing/ # UnifiedEmailServer, EmailRouter, DomainRegistry, DnsManager
|
||||||
│ │ └── security/ # DKIMCreator, DKIMVerifier, SpfVerifier, DmarcVerifier
|
│ │ └── security/ # DKIMCreator, DKIMVerifier, SpfVerifier, DmarcVerifier
|
||||||
│ └── security/ # ContentScanner, IPReputationChecker, SecurityLogger
|
│ └── security/ # ContentScanner, IPReputationChecker, RustSecurityBridge
|
||||||
├── rust/ # Rust workspace
|
├── rust/ # Rust workspace
|
||||||
│ └── crates/ # mailer-core, mailer-security, mailer-bin, mailer-smtp, mailer-napi
|
│ └── crates/ # mailer-core, mailer-security, mailer-bin, mailer-smtp, mailer-napi
|
||||||
├── test/ # Comprehensive test suite (RFC compliance, security, performance, edge cases)
|
├── test/ # Comprehensive test suite
|
||||||
└── dist_ts/ # Compiled output
|
└── dist_ts/ # Compiled output
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
136
test/test.rustsecuritybridge.node.ts
Normal file
136
test/test.rustsecuritybridge.node.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { RustSecurityBridge } from '../ts/security/classes.rustsecuritybridge.js';
|
||||||
|
|
||||||
|
let bridge: RustSecurityBridge;
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - should get singleton instance', async () => {
|
||||||
|
bridge = RustSecurityBridge.getInstance();
|
||||||
|
expect(bridge).toBeTruthy();
|
||||||
|
expect(bridge).toEqual(RustSecurityBridge.getInstance()); // same instance
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - should start the Rust binary', async () => {
|
||||||
|
const ok = await bridge.start();
|
||||||
|
if (!ok) {
|
||||||
|
console.log('WARNING: Rust binary not available — skipping bridge tests');
|
||||||
|
console.log('Build it with: cd rust && cargo build --release');
|
||||||
|
}
|
||||||
|
// We accept both true and false — the binary may not be built yet
|
||||||
|
expect(typeof ok).toEqual('boolean');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - ping should return pong', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pong = await bridge.ping();
|
||||||
|
expect(pong).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - version should return crate versions', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const version = await bridge.getVersion();
|
||||||
|
expect(version.bin).toBeTruthy();
|
||||||
|
expect(version.core).toBeTruthy();
|
||||||
|
expect(version.security).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - validateEmail with valid address', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await bridge.validateEmail('test@example.com');
|
||||||
|
expect(result.valid).toBeTrue();
|
||||||
|
expect(result.formatValid).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - validateEmail with invalid address', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await bridge.validateEmail('not-an-email');
|
||||||
|
expect(result.formatValid).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - detectBounce with known SMTP response', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await bridge.detectBounce({
|
||||||
|
smtpResponse: '550 5.1.1 User unknown',
|
||||||
|
});
|
||||||
|
expect(result.bounce_type).toBeTruthy();
|
||||||
|
expect(result.category).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - checkIpReputation', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Use a well-known IP that should NOT be on blacklists
|
||||||
|
const result = await bridge.checkIpReputation('8.8.8.8');
|
||||||
|
expect(result.ip).toEqual('8.8.8.8');
|
||||||
|
expect(typeof result.score).toEqual('number');
|
||||||
|
expect(result.score).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - verifyDkim with unsigned message', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawMessage = 'From: test@example.com\r\nTo: receiver@example.com\r\nSubject: Test\r\n\r\nHello';
|
||||||
|
const results = await bridge.verifyDkim(rawMessage);
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].status).toEqual('none'); // no DKIM signature
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - verifyEmail compound call', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rawMessage = 'From: test@example.com\r\nTo: receiver@example.com\r\nSubject: Test\r\n\r\nHello';
|
||||||
|
const result = await bridge.verifyEmail({
|
||||||
|
rawMessage,
|
||||||
|
ip: '93.184.216.34', // example.com IP
|
||||||
|
heloDomain: 'example.com',
|
||||||
|
hostname: 'mail.test.local',
|
||||||
|
mailFrom: 'test@example.com',
|
||||||
|
});
|
||||||
|
expect(result.dkim).toBeTruthy();
|
||||||
|
expect(result.dkim.length).toBeGreaterThan(0);
|
||||||
|
expect(result.spf).toBeTruthy();
|
||||||
|
// DMARC may or may not be present depending on DNS
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - should stop gracefully', async () => {
|
||||||
|
if (!bridge.running) {
|
||||||
|
console.log('SKIP: bridge not running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await bridge.stop();
|
||||||
|
expect(bridge.running).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('RustSecurityBridge - commands should fail when bridge is stopped', async () => {
|
||||||
|
// Bridge should not be running now
|
||||||
|
expect(bridge.running).toBeFalse();
|
||||||
|
try {
|
||||||
|
await bridge.ping();
|
||||||
|
// If we get here, the bridge auto-restarted or something unexpected
|
||||||
|
expect(true).toBeFalse(); // Should not reach here
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toBeTruthy(); // Expected: bridge not running error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartmta',
|
name: '@push.rocks/smartmta',
|
||||||
version: '2.0.0',
|
version: '2.0.1',
|
||||||
description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
|
description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,12 +656,12 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
const dkimSummary = result.dkim
|
const dkimSummary = result.dkim
|
||||||
.map(d => `${d.status}${d.domain ? ` (${d.domain})` : ''}`)
|
.map(d => `${d.status}${d.domain ? ` (${d.domain})` : ''}`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
email.setHeader('X-DKIM-Result', dkimSummary);
|
email.addHeader('X-DKIM-Result', dkimSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply SPF result header
|
// Apply SPF result header
|
||||||
if (result.spf) {
|
if (result.spf) {
|
||||||
email.setHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
email.addHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
||||||
|
|
||||||
// Mark as spam on SPF hard fail
|
// Mark as spam on SPF hard fail
|
||||||
if (result.spf.result === 'fail') {
|
if (result.spf.result === 'fail') {
|
||||||
@@ -672,7 +672,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|||||||
|
|
||||||
// Apply DMARC result header and policy
|
// Apply DMARC result header and policy
|
||||||
if (result.dmarc) {
|
if (result.dmarc) {
|
||||||
email.setHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
email.addHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
||||||
|
|
||||||
if (result.dmarc.action === 'reject') {
|
if (result.dmarc.action === 'reject') {
|
||||||
email.mightBeSpam = true;
|
email.mightBeSpam = true;
|
||||||
|
|||||||
@@ -46,11 +46,7 @@ interface IValidationResult {
|
|||||||
|
|
||||||
interface IBounceDetection {
|
interface IBounceDetection {
|
||||||
bounce_type: string;
|
bounce_type: string;
|
||||||
severity: string;
|
|
||||||
category: string;
|
category: string;
|
||||||
should_retry: boolean;
|
|
||||||
recommended_action: string;
|
|
||||||
details: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IReputationResult {
|
interface IReputationResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user