22 Commits

Author SHA1 Message Date
04e73c366c v5.3.1
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 5s
Publish to npm / npm-publish (push) Failing after 6s
Release / build-and-release (push) Failing after 4s
2026-03-02 14:06:47 +00:00
8851d61466 fix(mail): add periodic cleanup timers and proper shutdown handling for bounce manager and delivery queue; avoid mutating maps during iteration and prune stale rate-limiter stats to prevent memory growth 2026-03-02 14:06:47 +00:00
b465b01790 v5.3.0
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 4s
2026-02-26 17:50:52 +00:00
6ed3252485 feat(mailer-bin): use mimalloc as the global allocator for mailer-bin 2026-02-26 17:50:52 +00:00
fc88555790 v5.2.6
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 4s
2026-02-26 17:08:29 +00:00
4eb2fe7934 fix(postinstall): remove legacy postinstall binary installer and packaging entry 2026-02-26 17:08:29 +00:00
438242df07 v5.2.5
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 6s
Release / build-and-release (push) Failing after 5s
2026-02-26 17:06:40 +00:00
1bb48b2530 fix(package): remove CLI bin wrapper and exclude bin/ from published files 2026-02-26 17:06:40 +00:00
3e76662933 v5.2.4
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 4s
2026-02-26 17:02:19 +00:00
efb49b67c6 fix(repo): no changes detected — no version bump required 2026-02-26 17:02:19 +00:00
bbe56247bd v5.2.3
Some checks failed
CI / Type Check & Lint (push) Failing after 7s
CI / Build Test (Current Platform) (push) Failing after 6s
CI / Build All Platforms (push) Failing after 5s
Publish to npm / npm-publish (push) Failing after 7s
Release / build-and-release (push) Failing after 6s
2026-02-26 16:58:06 +00:00
71a0ec3202 fix(delivery): prevent throttle reset timer from firing after stop and avoid scheduling duplicate timers 2026-02-26 16:58:06 +00:00
cda2c06087 v5.2.2
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 6s
Release / build-and-release (push) Failing after 5s
2026-02-12 22:59:35 +00:00
a762c9acd0 fix(deps): bump dependencies: @push.rocks/smartrust to ^1.2.1, lru-cache to ^11.2.6 2026-02-12 22:59:35 +00:00
6cdc619cd0 v5.2.1
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 4s
2026-02-11 16:23:43 +00:00
c3d4c4abb5 fix(rust-bridge): map Node.js platform/arch to tsrust-style suffix and add platform-specific and dev localPaths for RustBridge 2026-02-11 16:23:43 +00:00
08c5145d20 v5.2.0
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 4s
2026-02-11 16:06:34 +00:00
0515d2ae46 feat(packaging): add package exports entry, include ts/dist_ts in package files, and add TS barrel index re-exports 2026-02-11 16:06:34 +00:00
96b4ccb7d3 v5.1.3
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 5s
Release / build-and-release (push) Failing after 5s
2026-02-11 14:24:07 +00:00
7c0c327913 fix(docs): clarify sendEmail default behavior and document automatic MX discovery and delivery modes 2026-02-11 14:24:07 +00:00
9e722874b4 v5.1.2
Some checks failed
CI / Type Check & Lint (push) Failing after 4s
CI / Build Test (Current Platform) (push) Failing after 4s
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 4s
Release / build-and-release (push) Failing after 4s
2026-02-11 10:20:19 +00:00
873af43ef2 fix(readme): adjust ASCII architecture diagram alignment in README 2026-02-11 10:20:19 +00:00
16 changed files with 321 additions and 304 deletions

View File

@@ -1,5 +1,82 @@
# Changelog # Changelog
## 2026-03-02 - 5.3.1 - fix(mail)
add periodic cleanup timers and proper shutdown handling for bounce manager and delivery queue; avoid mutating maps during iteration and prune stale rate-limiter stats to prevent memory growth
- BounceManager: add cleanupInterval to periodically remove bounce records older than 7 days and log removals; add stop() to clear the interval and prevent leaks
- UnifiedDeliveryQueue: introduce cleanupTimer started in startProcessing() and cleared in stopProcessing(); cleanupOldItems now collects IDs first to avoid mutating the Map while iterating and logs cleaned items; shutdown now relies on stopProcessing to clear timers
- UnifiedRateLimiter: prune stale stats.byIp and stats.byPattern entries for IPs/patterns that no longer have active counters or blocks to reduce memory usage and keep stats accurate
- Auto-cleanup tasks log errors rather than throwing to avoid crashing processing loops
## 2026-02-26 - 5.3.0 - feat(mailer-bin)
use mimalloc as the global allocator for mailer-bin
- Add mimalloc dependency to workspace Cargo.toml
- Enable workspace mimalloc in rust/crates/mailer-bin/Cargo.toml
- Register mimalloc as the #[global_allocator] in mailer-bin/src/main.rs
- Update Cargo.lock with new mimalloc and libmimalloc-sys entries
## 2026-02-26 - 5.2.6 - fix(postinstall)
remove legacy postinstall binary installer and packaging entry
- Deleted scripts/install-binary.js (legacy postinstall script that downloaded platform-specific binaries).
- Removed reference to scripts/install-binary.js from package.json "files" array so the installer is no longer included in published packages.
- This prevents automatic binary downloads during npm install and reduces package size; recommend a patch version bump.
## 2026-02-26 - 5.2.5 - fix(package)
remove CLI bin wrapper and exclude bin/ from published files
- Removed "bin" entry from package.json (mailer wrapper)
- Removed "bin/" from files array to prevent including CLI wrapper in published package
## 2026-02-26 - 5.2.4 - fix(repo)
no changes detected — no version bump required
- git diff contains no changes
- package.json version is 5.2.3
- no files modified — no release required
## 2026-02-26 - 5.2.3 - fix(delivery)
prevent throttle reset timer from firing after stop and avoid scheduling duplicate timers
- add throttleResetTimer property to track scheduled throttle-reset timeout
- clear throttleResetTimer when stopping to prevent it firing after shutdown
- clear existing throttleResetTimer before scheduling a new one and null it when fired to avoid duplicate timers and potential leaks
## 2026-02-12 - 5.2.2 - fix(deps)
bump dependencies: @push.rocks/smartrust to ^1.2.1, lru-cache to ^11.2.6
- Bumped @push.rocks/smartrust from ^1.2.0 to ^1.2.1
- Bumped lru-cache from ^11.2.5 to ^11.2.6
## 2026-02-11 - 5.2.1 - fix(rust-bridge)
map Node.js platform/arch to tsrust-style suffix and add platform-specific and dev localPaths for RustBridge
- Add getPlatformSuffix() to map process.platform/process.arch to tsrust-style suffixes (e.g. linux_amd64)
- Include dist_rust/mailer-bin_{suffix} when available to prefer cross-compiled binaries
- Consolidate localPaths and add local dev build paths (rust/target/release and rust/target/debug)
- Pass the computed localPaths array into plugins.smartrust.RustBridge (searchSystemPath remains disabled)
## 2026-02-11 - 5.2.0 - feat(packaging)
add package exports entry, include ts/dist_ts in package files, and add TS barrel index re-exports
- package.json: add "exports" mapping "." -> "./dist_ts/index.js" to provide a module entry point
- package.json: add "ts/**/*" and "dist_ts/**/*" to "files" so TypeScript sources and built output are published
- ts/index.ts: new barrel that re-exports './00_commitinfo_data.js', './mail/index.js', and './security/index.js'
## 2026-02-11 - 5.1.3 - fix(docs)
clarify sendEmail default behavior and document automatic MX discovery and delivery modes
- Updated README to describe automatic MX record discovery and grouping behavior when using sendEmail() (MTA mode)
- Added a Delivery Modes section and API signature for sendEmail(mode) describing mta, forward, and process options
- Expanded examples to show multi-recipient delivery, explicit mode usage, and retained low-level sendOutboundEmail example
## 2026-02-11 - 5.1.2 - fix(readme)
adjust ASCII architecture diagram alignment in README
- Whitespace and alignment tweaks to the ASCII architecture diagram in readme.md
- No code or behavior changes; documentation-only edit
## 2026-02-11 - 5.1.1 - fix(release) ## 2026-02-11 - 5.1.1 - fix(release)
no changes no changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@push.rocks/smartmta", "name": "@push.rocks/smartmta",
"version": "5.1.1", "version": "5.3.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",
@@ -27,8 +27,8 @@
"author": "Task Venture Capital GmbH", "author": "Task Venture Capital GmbH",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"bin": { "exports": {
"mailer": "./bin/mailer-wrapper.js" ".": "./dist_ts/index.js"
}, },
"scripts": { "scripts": {
"test": "tstest test/ --logfile --verbose --timeout 60", "test": "tstest test/ --logfile --verbose --timeout 60",
@@ -49,15 +49,15 @@
"@push.rocks/smartlog": "^3.1.8", "@push.rocks/smartlog": "^3.1.8",
"@push.rocks/smartmail": "^2.2.0", "@push.rocks/smartmail": "^2.2.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartrust": "^1.2.0", "@push.rocks/smartrust": "^1.2.1",
"@tsclass/tsclass": "^9.2.0", "@tsclass/tsclass": "^9.2.0",
"lru-cache": "^11.2.5", "lru-cache": "^11.2.6",
"mailparser": "^3.9.3", "mailparser": "^3.9.3",
"uuid": "^13.0.0" "uuid": "^13.0.0"
}, },
"files": [ "files": [
"bin/", "ts/**/*",
"scripts/install-binary.js", "dist_ts/**/*",
"dist_rust/**/*", "dist_rust/**/*",
"readme.md", "readme.md",
"license", "license",

40
pnpm-lock.yaml generated
View File

@@ -24,14 +24,14 @@ importers:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
'@push.rocks/smartrust': '@push.rocks/smartrust':
specifier: ^1.2.0 specifier: ^1.2.1
version: 1.2.0 version: 1.2.1
'@tsclass/tsclass': '@tsclass/tsclass':
specifier: ^9.2.0 specifier: ^9.2.0
version: 9.3.0 version: 9.3.0
lru-cache: lru-cache:
specifier: ^11.2.5 specifier: ^11.2.6
version: 11.2.5 version: 11.2.6
mailparser: mailparser:
specifier: ^3.9.3 specifier: ^3.9.3
version: 3.9.3 version: 3.9.3
@@ -69,6 +69,9 @@ packages:
'@api.global/typedrequest@3.2.5': '@api.global/typedrequest@3.2.5':
resolution: {integrity: sha512-LM/sUTuYnU5xY4gNZrN6ERMiKr+SpDZuSxJkAZz1YazC7ymGfo6uQ8sCnN8eNNQNFqIOkC+BtfYRayfbGwYLLg==} resolution: {integrity: sha512-LM/sUTuYnU5xY4gNZrN6ERMiKr+SpDZuSxJkAZz1YazC7ymGfo6uQ8sCnN8eNNQNFqIOkC+BtfYRayfbGwYLLg==}
'@api.global/typedrequest@3.2.6':
resolution: {integrity: sha512-CnvbjYjnGGw3rwL+7bTHSgRHEpDujzhs3cv7l1xgCXMPQe3DcPg74+9ep1Y5cu21T/w0pxNnDCJpbb0SHqHzAw==}
'@api.global/typedserver@3.0.80': '@api.global/typedserver@3.0.80':
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==} resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
@@ -830,8 +833,8 @@ packages:
'@push.rocks/smartrouter@1.3.3': '@push.rocks/smartrouter@1.3.3':
resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==} resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==}
'@push.rocks/smartrust@1.2.0': '@push.rocks/smartrust@1.2.1':
resolution: {integrity: sha512-JlaALselIHoP6C3ceQbrvz424G21cND/QsH/KI3E/JrO4XphJiGZwM6f4yJWrijdPYR/YYMoaIiYN7ybZp0C4w==} resolution: {integrity: sha512-ANwXXibUwoHNWF1hhXhXVVrfzYlhgHYRa2205Jkd/s/wXzcWHftYZthilJj+52B7nkzSB76umfxKfK5eBYY2Ug==}
'@push.rocks/smartrx@3.0.10': '@push.rocks/smartrx@3.0.10':
resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==}
@@ -2491,8 +2494,8 @@ packages:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
lru-cache@11.2.5: lru-cache@11.2.6:
resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
lru-cache@7.18.3: lru-cache@7.18.3:
@@ -3594,6 +3597,19 @@ snapshots:
'@push.rocks/webrequest': 3.0.37 '@push.rocks/webrequest': 3.0.37
'@push.rocks/webstream': 1.0.10 '@push.rocks/webstream': 1.0.10
'@api.global/typedrequest@3.2.6':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/isounique': 1.0.5
'@push.rocks/lik': 6.2.2
'@push.rocks/smartbuffer': 3.0.5
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartguard': 3.1.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/webrequest': 4.0.1
'@push.rocks/webstream': 1.0.10
optional: true
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@2.0.1)': '@api.global/typedserver@3.0.80(@push.rocks/smartserve@2.0.1)':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
@@ -5326,7 +5342,7 @@ snapshots:
'@push.rocks/smartrx': 3.0.10 '@push.rocks/smartrx': 3.0.10
path-to-regexp: 8.3.0 path-to-regexp: 8.3.0
'@push.rocks/smartrust@1.2.0': '@push.rocks/smartrust@1.2.1':
dependencies: dependencies:
'@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpath': 6.0.0
@@ -5347,7 +5363,7 @@ snapshots:
'@push.rocks/smartserve@2.0.1': '@push.rocks/smartserve@2.0.1':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.6
'@cfworker/json-schema': 4.1.1 '@cfworker/json-schema': 4.1.1
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0 '@push.rocks/smartenv': 6.0.0
@@ -7313,7 +7329,7 @@ snapshots:
lowercase-keys@3.0.0: {} lowercase-keys@3.0.0: {}
lru-cache@11.2.5: {} lru-cache@11.2.6: {}
lru-cache@7.18.3: {} lru-cache@7.18.3: {}
@@ -7920,7 +7936,7 @@ snapshots:
path-scurry@2.0.1: path-scurry@2.0.1:
dependencies: dependencies:
lru-cache: 11.2.5 lru-cache: 11.2.6
minipass: 7.1.2 minipass: 7.1.2
path-to-regexp@8.3.0: {} path-to-regexp@8.3.0: {}

View File

@@ -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 a Rust-powered SMTP engine — no nodemailer, no shortcuts. 🚀 A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with a Rust-powered SMTP engine — no nodemailer, no shortcuts. Automatic MX record discovery means you just call `sendEmail()` and smartmta figures out where to deliver. 🚀
## Issue Reporting and Security ## Issue Reporting and Security
@@ -43,28 +43,28 @@ After installation, run `pnpm build` to compile the Rust binary (`mailer-bin`).
``` ```
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ UnifiedEmailServer │ UnifiedEmailServer │
│ (orchestrates all components, emits events) │ (orchestrates all components, emits events) │
├───────────┬───────────┬──────────────┬───────────────────────┤ ├───────────┬───────────┬──────────────┬───────────────────────┤
│ Email │ Security │ Delivery │ Configuration │ │ Email │ Security │ Delivery │ Configuration │
│ Router │ Stack │ System │ │ │ Router │ Stack │ System │ │
│ ┌──────┐ │ ┌───────┐ │ ┌──────────┐ │ ┌────────────────┐ │ │ ┌──────┐ │ ┌───────┐ │ ┌──────────┐ │ ┌────────────────┐
│ │Match │ │ │ DKIM │ │ │ Queue │ │ │ DomainRegistry │ │ │ │Match │ │ │ DKIM │ │ │ Queue │ │ │ DomainRegistry │
│ │Route │ │ │ SPF │ │ │ Rate Lim │ │ │ DnsManager │ │ │ │Route │ │ │ SPF │ │ │ Rate Lim │ │ │ DnsManager │
│ │ Act │ │ │ DMARC │ │ │ Retry │ │ │ DKIMCreator │ │ │ │ Act │ │ │ DMARC │ │ │ Retry │ │ │ DKIMCreator │
│ └──────┘ │ │ IPRep │ │ └──────────┘ │ │ Templates │ │ │ └──────┘ │ │ IPRep │ │ └──────────┘ │ │ Templates │
│ │ │ Scan │ │ │ └────────────────┘ │ │ │ │ Scan │ │ │ └────────────────┘
│ │ └───────┘ │ │ │ │ │ └───────┘ │ │ │
├───────────┴───────────┴──────────────┴───────────────────────┤ ├───────────┴───────────┴──────────────┴───────────────────────┤
│ Rust Security Bridge (smartrust IPC) │ Rust Security Bridge (smartrust IPC) │
├──────────────────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────────────────┤
│ Rust Acceleration Layer │ Rust Acceleration Layer │
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ │ │ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐
│ │ mailer-smtp │ │mailer-security│ │ mailer-core │ │ │ │ mailer-smtp │ │mailer-security│ │ mailer-core │
│ │ SMTP Server │ │DKIM/SPF/DMARC │ │ Types/Validation │ │ │ │ SMTP Server │ │DKIM/SPF/DMARC │ │ Types/Validation │
│ │ SMTP Client │ │IP Rep/Content │ │ MIME/Bounce │ │ │ │ SMTP Client │ │IP Rep/Content │ │ MIME/Bounce │
│ │ TLS/AUTH │ │ Scanning │ │ Detection │ │ │ │ TLS/AUTH │ │ Scanning │ │ Detection │
│ └──────────────┘ └───────────────┘ └──────────────────┘ │ │ └──────────────┘ └───────────────┘ └──────────────────┘
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
``` ```
@@ -78,9 +78,10 @@ After installation, run `pnpm build` to compile the Rust binary (`mailer-bin`).
**Data flow for outbound mail:** **Data flow for outbound mail:**
1. 📝 TypeScript constructs the email and resolves DKIM keys for the sender domain 1. 📝 TypeScript constructs the email and calls `sendEmail()` (defaults to MTA mode)
2. 🦀 Sends to Rust via IPC — Rust builds the RFC 2822 message, signs with DKIM, and delivers via its SMTP client with connection pooling 2. 🔍 MTA mode automatically resolves MX records for each recipient domain, sorts by priority, and groups recipients for efficient delivery
3. 📬 Result (accepted/rejected recipients, server response) returned to TypeScript 3. 🦀 Sends to Rust via IPC — Rust builds the RFC 2822 message, signs with DKIM, and delivers via its SMTP client with connection pooling
4. 📬 Result (accepted/rejected recipients, server response) returned to TypeScript
## Usage ## Usage
@@ -169,9 +170,9 @@ await emailServer.start();
> 🔒 **Note:** `start()` will throw if the Rust binary is not compiled. Run `pnpm build` first. > 🔒 **Note:** `start()` will throw if the Rust binary is not compiled. Run `pnpm build` first.
### 📧 Sending Outbound Emails ### 📧 Sending Emails (Automatic MX Discovery)
All outbound email delivery goes through the Rust SMTP client, accessed via `UnifiedEmailServer.sendOutboundEmail()`. The Rust client handles connection pooling, TLS negotiation, and DKIM signing automatically: The recommended way to send email is `sendEmail()`. It defaults to **MTA mode**, which automatically resolves MX records for each recipient domain via DNS — you don't need to know the destination mail server:
```typescript ```typescript
import { Email, UnifiedEmailServer } from '@push.rocks/smartmta'; import { Email, UnifiedEmailServer } from '@push.rocks/smartmta';
@@ -179,8 +180,7 @@ import { Email, UnifiedEmailServer } from '@push.rocks/smartmta';
// Build an email // Build an email
const email = new Email({ const email = new Email({
from: 'sender@example.com', from: 'sender@example.com',
to: ['recipient@example.com'], to: ['alice@gmail.com', 'bob@company.org'],
cc: ['cc@example.com'],
subject: 'Hello from smartmta! 🚀', subject: 'Hello from smartmta! 🚀',
text: 'Plain text body', text: 'Plain text body',
html: '<h1>Hello!</h1><p>HTML body with <strong>formatting</strong></p>', html: '<h1>Hello!</h1><p>HTML body with <strong>formatting</strong></p>',
@@ -194,7 +194,50 @@ const email = new Email({
], ],
}); });
// Send via the Rust SMTP client (connection pooling, TLS, DKIM signing) // Send — MTA mode auto-discovers MX servers for gmail.com and company.org
const emailId = await emailServer.sendEmail(email);
// Optionally specify a delivery mode explicitly
const emailId2 = await emailServer.sendEmail(email, 'mta');
```
In MTA mode, smartmta:
- 🔍 Resolves MX records for each recipient domain (e.g. `gmail.com`, `company.org`)
- 📊 Sorts MX hosts by priority (lowest = highest priority per RFC 5321)
- 🔄 Tries each MX host in order until delivery succeeds
- 🌐 Falls back to the domain's A record if no MX records exist
- 📦 Groups recipients by domain for efficient batch delivery
- 🔑 Signs outbound mail with DKIM automatically
### 📮 Delivery Modes
`sendEmail()` accepts a mode parameter that controls how the email is delivered:
```typescript
public async sendEmail(
email: Email,
mode: EmailProcessingMode = 'mta', // 'mta' | 'forward' | 'process'
route?: IEmailRoute,
options?: {
skipSuppressionCheck?: boolean;
ipAddress?: string;
isTransactional?: boolean;
}
): Promise<string>
```
| Mode | Description |
|---|---|
| `mta` (default) | **Auto MX discovery** — resolves MX records via DNS, delivers directly to the recipient's mail server. No relay configuration needed. |
| `forward` | **Relay delivery** — forwards the email to a configured SMTP host (e.g. an internal mail gateway or third-party relay). |
| `process` | **Scan + deliver** — runs the content scanning / security pipeline first, then delivers via auto MX resolution. |
### 📬 Direct SMTP Delivery (Low-Level)
For cases where you know the exact target SMTP server (e.g. relaying to a specific host), use the lower-level `sendOutboundEmail()`:
```typescript
// Send directly to a known SMTP server (bypasses MX resolution)
const result = await emailServer.sendOutboundEmail('smtp.example.com', 587, email, { const result = await emailServer.sendOutboundEmail('smtp.example.com', 587, email, {
auth: { user: 'sender@example.com', pass: 'your-password' }, auth: { user: 'sender@example.com', pass: 'your-password' },
dkimDomain: 'example.com', dkimDomain: 'example.com',

20
rust/Cargo.lock generated
View File

@@ -894,6 +894,16 @@ version = "0.2.181"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.11.0" version = "0.11.0"
@@ -982,6 +992,7 @@ dependencies = [
"mailer-core", "mailer-core",
"mailer-security", "mailer-security",
"mailer-smtp", "mailer-smtp",
"mimalloc",
"rustls", "rustls",
"serde", "serde",
"serde_json", "serde_json",
@@ -1063,6 +1074,15 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mimalloc"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
dependencies = [
"libmimalloc-sys",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"

View File

@@ -32,3 +32,4 @@ clap = { version = "4", features = ["derive"] }
sha2 = "0.10" sha2 = "0.10"
hmac = "0.12" hmac = "0.12"
pbkdf2 = { version = "0.12", default-features = false } pbkdf2 = { version = "0.12", default-features = false }
mimalloc = "0.1"

View File

@@ -22,3 +22,4 @@ dashmap.workspace = true
base64.workspace = true base64.workspace = true
uuid.workspace = true uuid.workspace = true
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
mimalloc = { workspace = true }

View File

@@ -5,6 +5,9 @@
//! 2. **Management mode** (`--management`) — JSON-over-stdin/stdout IPC for //! 2. **Management mode** (`--management`) — JSON-over-stdin/stdout IPC for
//! integration with `@push.rocks/smartrust` from TypeScript //! integration with `@push.rocks/smartrust` from TypeScript
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use dashmap::DashMap; use dashmap::DashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@@ -1,230 +0,0 @@
#!/usr/bin/env node
/**
* MAILER npm postinstall script
* Downloads the appropriate binary for the current platform from GitHub releases
*/
import { platform, arch } from 'os';
import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import https from 'https';
import { pipeline } from 'stream';
import { promisify } from 'util';
import { createWriteStream } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const streamPipeline = promisify(pipeline);
// Configuration
const REPO_BASE = 'https://code.foss.global/serve.zone/mailer';
const VERSION = process.env.npm_package_version || '1.0.0';
function getBinaryInfo() {
const plat = platform();
const architecture = arch();
const platformMap = {
'darwin': 'macos',
'linux': 'linux',
'win32': 'windows'
};
const archMap = {
'x64': 'x64',
'arm64': 'arm64'
};
const mappedPlatform = platformMap[plat];
const mappedArch = archMap[architecture];
if (!mappedPlatform || !mappedArch) {
return { supported: false, platform: plat, arch: architecture };
}
let binaryName = `mailer-${mappedPlatform}-${mappedArch}`;
if (plat === 'win32') {
binaryName += '.exe';
}
return {
supported: true,
platform: mappedPlatform,
arch: mappedArch,
binaryName,
originalPlatform: plat
};
}
function downloadFile(url, destination) {
return new Promise((resolve, reject) => {
console.log(`Downloading from: ${url}`);
// Follow redirects
const download = (url, redirectCount = 0) => {
if (redirectCount > 5) {
reject(new Error('Too many redirects'));
return;
}
https.get(url, (response) => {
if (response.statusCode === 301 || response.statusCode === 302) {
console.log(`Following redirect to: ${response.headers.location}`);
download(response.headers.location, redirectCount + 1);
return;
}
if (response.statusCode !== 200) {
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
return;
}
const totalSize = parseInt(response.headers['content-length'], 10);
let downloadedSize = 0;
let lastProgress = 0;
response.on('data', (chunk) => {
downloadedSize += chunk.length;
const progress = Math.round((downloadedSize / totalSize) * 100);
// Only log every 10% to reduce noise
if (progress >= lastProgress + 10) {
console.log(`Download progress: ${progress}%`);
lastProgress = progress;
}
});
const file = createWriteStream(destination);
pipeline(response, file, (err) => {
if (err) {
reject(err);
} else {
console.log('Download complete!');
resolve();
}
});
}).on('error', reject);
};
download(url);
});
}
async function main() {
console.log('===========================================');
console.log(' MAILER - Binary Installation');
console.log('===========================================');
console.log('');
const binaryInfo = getBinaryInfo();
if (!binaryInfo.supported) {
console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`);
console.error('');
console.error('Supported platforms:');
console.error(' • Linux (x64, arm64)');
console.error(' • macOS (x64, arm64)');
console.error(' • Windows (x64)');
console.error('');
console.error('If you believe your platform should be supported, please file an issue:');
console.error(' https://code.foss.global/serve.zone/mailer/issues');
process.exit(1);
}
console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`);
console.log(`Architecture: ${binaryInfo.arch}`);
console.log(`Binary: ${binaryInfo.binaryName}`);
console.log(`Version: ${VERSION}`);
console.log('');
// Create dist/binaries directory if it doesn't exist
const binariesDir = join(__dirname, '..', 'dist', 'binaries');
if (!existsSync(binariesDir)) {
console.log('Creating binaries directory...');
mkdirSync(binariesDir, { recursive: true });
}
const binaryPath = join(binariesDir, binaryInfo.binaryName);
// Check if binary already exists and skip download
if (existsSync(binaryPath)) {
console.log('✓ Binary already exists, skipping download');
} else {
// Construct download URL
// Try release URL first, fall back to raw branch if needed
const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`;
const fallbackUrl = `${REPO_BASE}/raw/branch/main/dist/binaries/${binaryInfo.binaryName}`;
console.log('Downloading platform-specific binary...');
console.log('This may take a moment depending on your connection speed.');
console.log('');
try {
// Try downloading from release
await downloadFile(releaseUrl, binaryPath);
} catch (err) {
console.log(`Release download failed: ${err.message}`);
console.log('Trying fallback URL...');
try {
// Try fallback URL
await downloadFile(fallbackUrl, binaryPath);
} catch (fallbackErr) {
console.error(`❌ Error: Failed to download binary`);
console.error(` Primary URL: ${releaseUrl}`);
console.error(` Fallback URL: ${fallbackUrl}`);
console.error('');
console.error('This might be because:');
console.error('1. The release has not been created yet');
console.error('2. Network connectivity issues');
console.error('3. The version specified does not exist');
console.error('');
console.error('You can try:');
console.error('1. Installing from source: https://code.foss.global/serve.zone/mailer');
console.error('2. Downloading the binary manually from the releases page');
// Clean up partial download
if (existsSync(binaryPath)) {
unlinkSync(binaryPath);
}
process.exit(1);
}
}
console.log(`✓ Binary downloaded successfully`);
}
// On Unix-like systems, ensure the binary is executable
if (binaryInfo.originalPlatform !== 'win32') {
try {
console.log('Setting executable permissions...');
chmodSync(binaryPath, 0o755);
console.log('✓ Binary permissions updated');
} catch (err) {
console.error(`⚠️ Warning: Could not set executable permissions: ${err.message}`);
console.error(' You may need to manually run:');
console.error(` chmod +x ${binaryPath}`);
}
}
console.log('');
console.log('✅ MAILER installation completed successfully!');
console.log('');
console.log('You can now use MAILER by running:');
console.log(' mailer --help');
console.log('');
console.log('For initial setup, run:');
console.log(' sudo mailer service enable');
console.log('');
console.log('===========================================');
}
// Run the installation
main().catch(err => {
console.error(`❌ Installation failed: ${err.message}`);
process.exit(1);
});

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartmta', name: '@push.rocks/smartmta',
version: '5.1.1', version: '5.3.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.'
} }

3
ts/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './00_commitinfo_data.js';
export * from './mail/index.js';
export * from './security/index.js';

View File

@@ -88,7 +88,10 @@ export class BounceManager {
// Store of bounced emails // Store of bounced emails
private bounceStore: BounceRecord[] = []; private bounceStore: BounceRecord[] = [];
// Periodic cleanup timer for old bounce records
private cleanupInterval?: NodeJS.Timeout;
// Cache of recently bounced email addresses to avoid sending to known bad addresses // Cache of recently bounced email addresses to avoid sending to known bad addresses
private bounceCache: LRUCache<string, { private bounceCache: LRUCache<string, {
lastBounce: number; lastBounce: number;
@@ -135,6 +138,15 @@ export class BounceManager {
this.loadSuppressionList().catch(error => { this.loadSuppressionList().catch(error => {
logger.log('error', `Failed to load suppression list on startup: ${error.message}`); logger.log('error', `Failed to load suppression list on startup: ${error.message}`);
}); });
// Start periodic cleanup of old bounce records (every 1 hour, removes records older than 7 days)
this.cleanupInterval = setInterval(() => {
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
const removed = this.clearOldBounceRecords(sevenDaysAgo);
if (removed > 0) {
logger.log('info', `Auto-cleanup removed ${removed} old bounce records`);
}
}, 60 * 60 * 1000);
} }
/** /**
@@ -717,7 +729,7 @@ export class BounceManager {
*/ */
public clearOldBounceRecords(olderThan: number): number { public clearOldBounceRecords(olderThan: number): number {
let removed = 0; let removed = 0;
this.bounceStore = this.bounceStore.filter(bounce => { this.bounceStore = this.bounceStore.filter(bounce => {
if (bounce.timestamp < olderThan) { if (bounce.timestamp < olderThan) {
removed++; removed++;
@@ -725,7 +737,17 @@ export class BounceManager {
} }
return true; return true;
}); });
return removed; return removed;
} }
/**
* Stop the bounce manager and clear cleanup timers
*/
public stop(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = undefined;
}
}
} }

View File

@@ -78,6 +78,7 @@ export class UnifiedDeliveryQueue extends EventEmitter {
private options: Required<IQueueOptions>; private options: Required<IQueueOptions>;
private queue: Map<string, IQueueItem> = new Map(); private queue: Map<string, IQueueItem> = new Map();
private checkTimer?: NodeJS.Timeout; private checkTimer?: NodeJS.Timeout;
private cleanupTimer?: NodeJS.Timeout;
private stats: IQueueStats; private stats: IQueueStats;
private processing: boolean = false; private processing: boolean = false;
private totalProcessed: number = 0; private totalProcessed: number = 0;
@@ -158,8 +159,19 @@ export class UnifiedDeliveryQueue extends EventEmitter {
if (this.checkTimer) { if (this.checkTimer) {
clearInterval(this.checkTimer); clearInterval(this.checkTimer);
} }
this.checkTimer = setInterval(() => this.processQueue(), this.options.checkInterval); this.checkTimer = setInterval(() => this.processQueue(), this.options.checkInterval);
// Start periodic cleanup of delivered/failed items (every 30 minutes)
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
this.cleanupTimer = setInterval(() => {
this.cleanupOldItems(24 * 60 * 60 * 1000).catch((err) => {
logger.log('error', `Auto-cleanup failed: ${err.message}`);
});
}, 30 * 60 * 1000);
this.processing = true; this.processing = true;
this.stats.processingActive = true; this.stats.processingActive = true;
this.emit('processingStarted'); this.emit('processingStarted');
@@ -174,7 +186,11 @@ export class UnifiedDeliveryQueue extends EventEmitter {
clearInterval(this.checkTimer); clearInterval(this.checkTimer);
this.checkTimer = undefined; this.checkTimer = undefined;
} }
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = undefined;
}
this.processing = false; this.processing = false;
this.stats.processingActive = false; this.stats.processingActive = false;
this.emit('processingStopped'); this.emit('processingStopped');
@@ -590,19 +606,24 @@ export class UnifiedDeliveryQueue extends EventEmitter {
*/ */
public async cleanupOldItems(maxAge: number = 7 * 24 * 60 * 60 * 1000): Promise<number> { public async cleanupOldItems(maxAge: number = 7 * 24 * 60 * 60 * 1000): Promise<number> {
const cutoff = new Date(Date.now() - maxAge); const cutoff = new Date(Date.now() - maxAge);
let removedCount = 0;
// Collect IDs first to avoid modifying the Map during iteration
// Find old items const idsToRemove: string[] = [];
for (const item of this.queue.values()) { for (const item of this.queue.values()) {
if (['delivered', 'failed'].includes(item.status) && item.updatedAt < cutoff) { if (['delivered', 'failed'].includes(item.status) && item.updatedAt < cutoff) {
// Remove item idsToRemove.push(item.id);
await this.removeItem(item.id);
removedCount++;
} }
} }
logger.log('info', `Cleaned up ${removedCount} old items`); // Remove collected items
return removedCount; for (const id of idsToRemove) {
await this.removeItem(id);
}
if (idsToRemove.length > 0) {
logger.log('info', `Cleaned up ${idsToRemove.length} old items from delivery queue`);
}
return idsToRemove.length;
} }
/** /**
@@ -611,15 +632,9 @@ export class UnifiedDeliveryQueue extends EventEmitter {
public async shutdown(): Promise<void> { public async shutdown(): Promise<void> {
logger.log('info', 'Shutting down UnifiedDeliveryQueue'); logger.log('info', 'Shutting down UnifiedDeliveryQueue');
// Stop processing // Stop processing (clears both check and cleanup timers)
this.stopProcessing(); this.stopProcessing();
// Clear the check timer to prevent memory leaks
if (this.checkTimer) {
clearInterval(this.checkTimer);
this.checkTimer = undefined;
}
// If using disk storage, make sure all items are persisted // If using disk storage, make sure all items are persisted
if (this.options.storageType === 'disk') { if (this.options.storageType === 'disk') {
const pendingWrites: Promise<void>[] = []; const pendingWrites: Promise<void>[] = [];

View File

@@ -108,6 +108,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
private activeDeliveries: Set<string> = new Set(); private activeDeliveries: Set<string> = new Set();
private running: boolean = false; private running: boolean = false;
private throttled: boolean = false; private throttled: boolean = false;
private throttleResetTimer: ReturnType<typeof setTimeout> | null = null;
private rateLimitLastCheck: number = Date.now(); private rateLimitLastCheck: number = Date.now();
private rateLimitCounter: number = 0; private rateLimitCounter: number = 0;
private emailServer?: UnifiedEmailServer; private emailServer?: UnifiedEmailServer;
@@ -211,7 +212,13 @@ export class MultiModeDeliverySystem extends EventEmitter {
} }
this.running = false; this.running = false;
// Clear throttle reset timer to prevent it firing after stop
if (this.throttleResetTimer) {
clearTimeout(this.throttleResetTimer);
this.throttleResetTimer = null;
}
// Wait for active deliveries to complete // Wait for active deliveries to complete
if (this.activeDeliveries.size > 0) { if (this.activeDeliveries.size > 0) {
logger.log('info', `Waiting for ${this.activeDeliveries.size} active deliveries to complete`); logger.log('info', `Waiting for ${this.activeDeliveries.size} active deliveries to complete`);
@@ -776,7 +783,11 @@ export class MultiModeDeliverySystem extends EventEmitter {
// Schedule throttle reset // Schedule throttle reset
const resetDelay = 60000 - elapsed; const resetDelay = 60000 - elapsed;
setTimeout(() => { if (this.throttleResetTimer) {
clearTimeout(this.throttleResetTimer);
}
this.throttleResetTimer = setTimeout(() => {
this.throttleResetTimer = null;
this.throttled = false; this.throttled = false;
this.rateLimitLastCheck = Date.now(); this.rateLimitLastCheck = Date.now();
this.rateLimitCounter = 0; this.rateLimitCounter = 0;

View File

@@ -231,7 +231,21 @@ export class UnifiedRateLimiter extends EventEmitter {
this.domainCounters.delete(key); this.domainCounters.delete(key);
} }
} }
// Clean stale stats.byIp entries for IPs that no longer have active counters or blocks
for (const ip of Object.keys(this.stats.byIp)) {
if (!this.ipCounters.has(ip) && !(this.config.blocks && ip in this.config.blocks)) {
delete this.stats.byIp[ip];
}
}
// Clean stale stats.byPattern entries for patterns that no longer have active counters
for (const pattern of Object.keys(this.stats.byPattern)) {
if (!this.patternCounters.has(pattern)) {
delete this.stats.byPattern[pattern];
}
}
// Update statistics // Update statistics
this.updateStats(); this.updateStats();
} }

View File

@@ -375,18 +375,39 @@ export class RustSecurityBridge extends EventEmitter {
private _deliberateStop = false; private _deliberateStop = false;
private _smtpServerConfig: ISmtpServerConfig | null = null; private _smtpServerConfig: ISmtpServerConfig | null = null;
/**
* Map Node.js process.platform / process.arch to the tsrust-style suffix
* used for cross-compiled binaries, e.g. mailer-bin_linux_amd64.
*/
private static getPlatformSuffix(): string | null {
const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
const os = process.platform; // 'linux', 'darwin', 'win32', …
const arch = archMap[process.arch];
if (!arch) return null;
return `${os}_${arch}`;
}
private constructor() { private constructor() {
super(); super();
const suffix = RustSecurityBridge.getPlatformSuffix();
const localPaths: string[] = [];
// dist_rust/ candidates (tsrust cross-compiled output)
if (suffix) {
localPaths.push(plugins.path.join(paths.packageDir, 'dist_rust', `mailer-bin_${suffix}`));
}
localPaths.push(plugins.path.join(paths.packageDir, 'dist_rust', 'mailer-bin'));
// Local dev build paths
localPaths.push(plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'));
localPaths.push(plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'));
this.bridge = new plugins.smartrust.RustBridge<TMailerCommands>({ this.bridge = new plugins.smartrust.RustBridge<TMailerCommands>({
binaryName: 'mailer-bin', binaryName: 'mailer-bin',
cliArgs: ['--management'], cliArgs: ['--management'],
requestTimeoutMs: 30_000, requestTimeoutMs: 30_000,
readyTimeoutMs: 10_000, readyTimeoutMs: 10_000,
localPaths: [ localPaths,
plugins.path.join(paths.packageDir, 'dist_rust', 'mailer-bin'),
plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'),
plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'),
],
searchSystemPath: false, searchSystemPath: false,
}); });