@push.rocks/smartmta 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +220 -0
- package/dist_rust/mailer-bin_linux_amd64 +0 -0
- package/dist_rust/mailer-bin_linux_arm64 +0 -0
- package/license +21 -0
- package/package.json +83 -0
- package/readme.hints.md +25 -0
- package/readme.md +582 -0
- package/readme.plan.md +24 -0
- package/scripts/install-binary.js +230 -0
package/changelog.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026-02-11 - 5.1.1 - fix(release)
|
|
4
|
+
no changes
|
|
5
|
+
|
|
6
|
+
- No files changed in this commit.
|
|
7
|
+
- Current package version remains 5.1.0 (from package.json).
|
|
8
|
+
|
|
9
|
+
## 2026-02-11 - 5.1.0 - feat(mailer-smtp)
|
|
10
|
+
add SCRAM-SHA-256 auth, Ed25519 DKIM, opportunistic TLS, SNI cert selection, pipelining and delivery/bridge improvements
|
|
11
|
+
|
|
12
|
+
- Add server-side SCRAM-SHA-256 implementation in Rust (scram.rs) and wire up SCRAM credential request/response between Rust and TypeScript bridge (ScramCredentialRequest / scramCredentialResult).
|
|
13
|
+
- Support SCRAM-SHA-256 auth mechanism in SMTP command parsing and advertise AUTH PLAIN LOGIN SCRAM-SHA-256 capability.
|
|
14
|
+
- Add opportunistic TLS mode for MTA-to-MTA delivery: configurable tls_opportunistic flag, an OpportunisticVerifier that skips cert verification per RFC 7435, and plumbing into connect/upgrade TLS paths.
|
|
15
|
+
- Add pipelined envelope support for MAIL FROM + multiple RCPT TO (send_pipelined_envelope) and use pipelining when server advertises PIPELINING to improve outbound performance.
|
|
16
|
+
- Add Ed25519 DKIM signing support and auto-dispatch: sign_dkim_ed25519, sign_dkim_auto, dkim_dns_record_value_typed, and TS changes to detect key type and call the auto signing API.
|
|
17
|
+
- Expose additional per-domain TLS certs (additionalTlsCerts) and implement SNI-based certificate resolver on the server to select certs by hostname; parsing helpers and fallback default cert handling included.
|
|
18
|
+
- Install ring crypto provider early in mailer-bin main for rustls operations and add related rust dependencies (sha2, hmac, pbkdf2) and workspace entries.
|
|
19
|
+
- TypeScript delivery and server bridge changes: group recipients by domain, MX resolution fallback to A record, MTA delivery loop over MX hosts, DKIM options propagation, TLS opportunistic option passed to outbound client, SCRAM credential computation in TS using PBKDF2/HMAC/SHA256 and sending results back to Rust.
|
|
20
|
+
- Add new tests and utilities: IPv6 DNSBL support and tests, SCRAM unit tests, DKIM Ed25519 tests, node-level MTA delivery integration test, and various test updates.
|
|
21
|
+
- Public API additions on the Rust <-> TS bridge: signDkim accepts keyType, new scram credential result command, onScramCredentialRequest/onScramCredentialResult helpers and sendScramCredentialResult.
|
|
22
|
+
- Various refactors and safety/feature improvements across mailer-core/smtp/security: envelope handling, stream buffering detection, and error handling for auth flows.
|
|
23
|
+
|
|
24
|
+
## 2026-02-11 - 5.0.0 - BREAKING CHANGE(mail)
|
|
25
|
+
remove DMARC and DKIM verifier implementations and MTA error classes; introduce DkimManager and EmailActionExecutor; simplify SPF verifier and update routing exports and tests
|
|
26
|
+
|
|
27
|
+
- Removed ts/mail/security/classes.dmarcverifier.ts and ts/mail/security/classes.dkimverifier.ts — DMARC and DKIM verifier implementations deleted
|
|
28
|
+
- Removed ts/errors/index.ts — MTA-specific error classes removed
|
|
29
|
+
- Added ts/mail/routing/classes.dkim.manager.ts — new DKIM key management and rotation logic
|
|
30
|
+
- Added ts/mail/routing/classes.email.action.executor.ts — centralized email action execution (forward/process/deliver/reject)
|
|
31
|
+
- Updated ts/mail/security/classes.spfverifier.ts to retain SPF parsing but removed verify/verifyAndApply logic delegating to Rust bridge
|
|
32
|
+
- Updated ts/mail/routing/index.ts to export new routing classes and adjusted import paths (e.g. delivery queue import updated)
|
|
33
|
+
- Tests trimmed: DMARC tests and rate limiter tests removed; SPF parsing test retained and simplified
|
|
34
|
+
- This set of changes alters public exports and removes previously available verifier APIs — major version bump recommended
|
|
35
|
+
|
|
36
|
+
## 2026-02-11 - 4.1.1 - fix(readme)
|
|
37
|
+
clarify architecture and IPC, document outbound flow and testing, and update module and crate descriptions in README
|
|
38
|
+
|
|
39
|
+
- Changed IPC description to JSON-over-stdin/stdout (clarifies communication format between Rust and TypeScript)
|
|
40
|
+
- Added Rust SMTP client entry and documented outbound mail data flow (TypeScript -> Rust signing/delivery -> result back)
|
|
41
|
+
- Expanded testing instructions with commands for building Rust binary and running unit/E2E tests
|
|
42
|
+
- Updated architecture diagram labels and Rust crate/module descriptions (mailer-smtp now includes client; test counts noted)
|
|
43
|
+
- Documentation-only changes; no source code behavior modified
|
|
44
|
+
|
|
45
|
+
## 2026-02-11 - 4.1.0 - feat(e2e-tests)
|
|
46
|
+
add Node.js end-to-end tests covering server lifecycle, inbound SMTP handling, outbound delivery and routing actions
|
|
47
|
+
|
|
48
|
+
- Adds four end-to-end test files: test.e2e.server-lifecycle.node.ts, test.e2e.inbound-smtp.node.ts, test.e2e.outbound-delivery.node.ts, test.e2e.routing-actions.node.ts
|
|
49
|
+
- Tests exercise UnifiedEmailServer start/stop, SMTP handshake and transactions, outbound delivery via a mock SMTP server, routing actions (process, deliver, reject, forward), concurrency, and RSET handling mid-session
|
|
50
|
+
- Introduces a minimal mock SMTP server to avoid IPC deadlock with the Rust SMTP client during outbound delivery tests
|
|
51
|
+
- Tests will skip when the Rust bridge or server cannot start (binary build required)
|
|
52
|
+
|
|
53
|
+
## 2026-02-11 - 4.0.0 - BREAKING CHANGE(smtp-client)
|
|
54
|
+
Replace the legacy TypeScript SMTP client with a new Rust-based SMTP client and IPC bridge for outbound delivery
|
|
55
|
+
|
|
56
|
+
- Introduce a Rust SMTP client crate with connection handling, TLS, protocol engine, and connection pooling (new modules: connection, pool, protocol, error, config).
|
|
57
|
+
- Add IPC handlers and management commands in the Rust binary: sendEmail, sendRawEmail, verifySmtpConnection, closeSmtpPool, getSmtpPoolStatus and integrate a SmtpClientManager into the runtime.
|
|
58
|
+
- Update TypeScript bridge (RustSecurityBridge) with new types and methods (ISmtpSendOptions, ISmtpSendResult, verifySmtpConnection, sendOutboundEmail, sendRawEmail, getSmtpPoolStatus, closeSmtpPool) and rework UnifiedEmailServer to use the Rust bridge for outbound delivery and DKIM signing.
|
|
59
|
+
- Remove the previous TypeScript SMTP client implementation and associated tests/utilities (many ts/mail/delivery/smtpclient modules and tests deleted) in favor of the Rust implementation.
|
|
60
|
+
- Bump dependencies and cargo config: @push.rocks/smartrust to ^1.2.0 in package.json and add/require crates (uuid, base64, webpki-roots) in Rust Cargo files.
|
|
61
|
+
|
|
62
|
+
## 2026-02-10 - 3.0.0 - BREAKING CHANGE(security)
|
|
63
|
+
implement resilience and lifecycle management for RustSecurityBridge (auto-restart, health checks, state machine and eventing); remove legacy TS SMTP test helper and DNSManager; remove deliverability IP-warmup/sender-reputation integrations and related types; drop unused dependencies
|
|
64
|
+
|
|
65
|
+
- RustSecurityBridge now extends EventEmitter and includes a BridgeState state machine, IBridgeResilienceConfig with DEFAULT_RESILIENCE_CONFIG, auto-restart with exponential backoff, periodic health checks, restart/restore logic, and descriptive ensureRunning() guards on command methods.
|
|
66
|
+
- Added static methods: resetInstance() (test-friendly) and configure(...) to tweak resilience settings at runtime.
|
|
67
|
+
- Added stateChange events and logging for lifecycle transitions; new tests added for resilience: test/test.rustsecuritybridge.resilience.node.ts.
|
|
68
|
+
- Removed the TypeScript SMTP test helper (test/helpers/server.loader.ts), the DNSManager (ts/mail/routing/classes.dnsmanager.ts), and many deliverability-related interfaces/implementations (IP warmup manager and sender reputation monitor) from unified email server.
|
|
69
|
+
- Removed public types ISmtpServerOptions and ISmtpTransactionResult from ts/mail/delivery/interfaces.ts, which is a breaking API change for consumers relying on those types.
|
|
70
|
+
- Removed unused dependencies from package.json: ip and mailauth.
|
|
71
|
+
|
|
72
|
+
## 2026-02-10 - 2.4.0 - feat(docs)
|
|
73
|
+
document Rust-side in-process security pipeline and update README to reflect SMTP server behavior and crate/test counts
|
|
74
|
+
|
|
75
|
+
- Clarifies that the Rust SMTP server accepts the full SMTP protocol and runs the security pipeline in-process (DKIM/SPF/DMARC verification, content scanning, IP reputation/DNSBL) to avoid IPC round-trips
|
|
76
|
+
- Notes that Rust now emits an emailReceived IPC event with pre-computed security results attached for TypeScript to use in routing/delivery decisions
|
|
77
|
+
- Updates mailer-smtp crate description to include the in-process security pipeline and increments its test count from 72 to 77
|
|
78
|
+
- Adjusts TypeScript directory comments to reflect removal/relocation of the legacy TS SMTP server and the smtpclient path
|
|
79
|
+
|
|
80
|
+
## 2026-02-10 - 2.3.2 - fix(tests)
|
|
81
|
+
remove large SMTP client test suites and update SmartFile API usage
|
|
82
|
+
|
|
83
|
+
- Deleted ~80 test files under test/suite/ (multiple smtpclient command, connection, edge-cases, email-composition, error-handling and performance test suites)
|
|
84
|
+
- Updated SmartFile usage in test/test.smartmail.ts: replaced plugins.smartfile.SmartFile.fromString(...) with plugins.smartfile.SmartFileFactory.nodeFs().fromString(...)
|
|
85
|
+
- Removes a large set of tests to reduce test surface / simplify test runtime
|
|
86
|
+
|
|
87
|
+
## 2026-02-10 - 2.3.1 - fix(npmextra)
|
|
88
|
+
update .gitignore and npmextra.json to add ignore patterns, registries, and module metadata
|
|
89
|
+
|
|
90
|
+
- .gitignore: expanded ignore list for artifacts, installs, caches, builds, AI dirs, and rust target path
|
|
91
|
+
- npmextra.json: added npmjs registry alongside internal Verdaccio registry for @git.zone/cli release settings
|
|
92
|
+
- npmextra.json: added projectType and module metadata (githost, gitscope, gitrepo, description, npmPackagename, license) for @git.zone/cli and added empty @ship.zone/szci entry
|
|
93
|
+
|
|
94
|
+
## 2026-02-10 - 2.3.0 - feat(mailer-smtp)
|
|
95
|
+
add in-process security pipeline for SMTP delivery (DKIM/SPF/DMARC, content scanning, IP reputation)
|
|
96
|
+
|
|
97
|
+
- Integrate mailer_security verification (DKIM/SPF/DMARC) and IP reputation checks into the Rust SMTP server; run concurrently and wrapped with a 30s timeout.
|
|
98
|
+
- Add MIME parsing using mailparse and an extract_mime_parts helper to extract subject, text/html bodies and attachment filenames for content scanning.
|
|
99
|
+
- Wire MessageAuthenticator and TokioResolver into server and connection startup; pass them into the delivery pipeline and connection handlers.
|
|
100
|
+
- Run content scanning (mailer_security::content_scanner), combine results (dkim/spf/dmarc, contentScan, ipReputation) into a JSON object and attach as security_results on EmailReceived events.
|
|
101
|
+
- Update Rust crates (Cargo.toml/Cargo.lock) to include mailparse and resolver usage and add serde::Deserialize where required; add unit tests for MIME extraction.
|
|
102
|
+
- Remove the TypeScript SMTP server implementation and many TS tests; replace test helper (server.loader.ts) with a stub that points tests to use the Rust SMTP server and provide small utilities (getAvailablePort/isPortFree).
|
|
103
|
+
|
|
104
|
+
## 2026-02-10 - 2.2.1 - fix(readme)
|
|
105
|
+
Clarify Rust-powered architecture and mandatory Rust bridge; expand README with Rust workspace details and project structure updates
|
|
106
|
+
|
|
107
|
+
- Emphasizes that the SMTP server is Rust-powered (high-performance) and not a nodemailer-based TS server.
|
|
108
|
+
- Documents that the Rust binary (mailer-bin) is required — if unavailable UnifiedEmailServer.start() will throw an error.
|
|
109
|
+
- Adds installation/build note: run `pnpm build` to compile the Rust binary.
|
|
110
|
+
- Adds a new Rust Acceleration Layer section listing workspace crates and responsibilities (mailer-core, mailer-security, mailer-smtp, mailer-bin, mailer-napi).
|
|
111
|
+
- Updates project structure: marks legacy TS SMTP server as fallback/legacy, adds dist_rust output, and clarifies which operations run in Rust vs TypeScript.
|
|
112
|
+
|
|
113
|
+
## 2026-02-10 - 2.2.0 - feat(mailer-smtp)
|
|
114
|
+
implement in-process SMTP server and management IPC integration
|
|
115
|
+
|
|
116
|
+
- Add full SMTP protocol engine crate (mailer-smtp) with modules: command, config, connection, data, response, session, state, validation, rate_limiter and server
|
|
117
|
+
- Introduce SmtpServerConfig, DataAccumulator (DATA phase handling, dot-unstuffing, size limits) and SmtpResponse builder with EHLO capability construction
|
|
118
|
+
- Add in-process RateLimiter using DashMap and runtime-configurable RateLimitConfig
|
|
119
|
+
- Add TCP/TLS server start/stop API (start_server) with TlsAcceptor building from PEM and SmtpServerHandle for shutdown and status
|
|
120
|
+
- Integrate callback registry and oneshot-based correlation callbacks in mailer-bin management mode for email processing/auth results and JSON IPC parsing for SmtpServerConfig
|
|
121
|
+
- TypeScript bridge and routing updates: new IPC commands/types (startSmtpServer, stopSmtpServer, emailProcessingResult, authResult, configureRateLimits) and event handlers (emailReceived, authRequest)
|
|
122
|
+
- Update Cargo manifests and lockfile to add dependencies (dashmap, regex, rustls, rustls-pemfile, rustls-pki-types, uuid, serde_json, base64, etc.)
|
|
123
|
+
- Add comprehensive unit tests for new modules (config, data, response, session, state, rate_limiter, validation)
|
|
124
|
+
|
|
125
|
+
## 2026-02-10 - 2.1.0 - feat(security)
|
|
126
|
+
migrate content scanning and bounce detection to Rust security bridge; add scanContent IPC command and Rust content scanner with tests; update TS RustSecurityBridge and callers, and adjust CI package references
|
|
127
|
+
|
|
128
|
+
- Add Rust content scanner implementation (rust/crates/mailer-security/src/content_scanner.rs) with pattern-based detection and unit tests (~515 lines)
|
|
129
|
+
- Expose new IPC command 'scanContent' in mailer-bin and marshal results via JSON for the RustSecurityBridge
|
|
130
|
+
- Update TypeScript RustSecurityBridge with scanContent typing and method, and replace local JS detection logic (bounce/content) to call Rust bridge
|
|
131
|
+
- Update tests to start/stop the RustSecurityBridge and rely on Rust-based detection (test updates in test.bouncemanager.ts and test.contentscanner.ts)
|
|
132
|
+
- Update CI workflow messages and package references from @serve.zone/mailer to @push.rocks/smartmta
|
|
133
|
+
- Add regex dependency to rust mailer-security workspace (Cargo.toml / Cargo.lock updated)
|
|
134
|
+
|
|
135
|
+
## 2026-02-10 - 2.0.1 - fix(docs/readme)
|
|
136
|
+
update README: clarify APIs, document RustSecurityBridge, update examples and architecture diagram
|
|
137
|
+
|
|
138
|
+
- Documented RustSecurityBridge: startup/shutdown, automatic delegation, compound verifyEmail API, and individual operations
|
|
139
|
+
- Clarified verification APIs: SpfVerifier.verify() and DmarcVerifier.verify() examples now take an Email object as the first argument
|
|
140
|
+
- Updated example method names/usages: scanEmail, createEmail, evaluateRoutes, checkMessageLimit, isEmailSuppressed, DKIMCreator rotation and output formatting
|
|
141
|
+
- Reformatted architecture diagram and added Rust Security Bridge and expanded Rust Acceleration details
|
|
142
|
+
- Rate limiter example updated: renamed/standardized config keys (maxMessagesPerMinute, domains) and added additional limits (maxRecipientsPerMessage, maxConnectionsPerIP, etc.)
|
|
143
|
+
- DNS management documentation reorganized: UnifiedEmailServer now handles DNS record setup automatically; DNSManager usage clarified for standalone checks
|
|
144
|
+
- Minor wording/formatting tweaks throughout README (arrow styles, headings, test counts)
|
|
145
|
+
|
|
146
|
+
## 2026-02-10 - 2.0.0 - BREAKING CHANGE(smartmta)
|
|
147
|
+
Rebrand package to @push.rocks/smartmta, add consolidated email security verification and IPC handler
|
|
148
|
+
|
|
149
|
+
- Package renamed from @serve.zone/mailer to @push.rocks/smartmta (package.json, commitinfo, README and homepage/bugs/repository URLs updated) — breaking for consumers who import by package name.
|
|
150
|
+
- Added new compound email security API verify_email_security that runs DKIM, SPF and DMARC in a single call (rust/crates/mailer-security/src/verify.rs) and exposed it from the mailer-security crate.
|
|
151
|
+
- Added IPC handler "verifyEmail" in mailer-bin to call the new verify_email_security function from the Rust side.
|
|
152
|
+
- Refactored DKIM and SPF code to convert mail-auth outputs to serializable results (dkim_outputs_to_results and SpfResult::from_output) and wired them into the combined verifier.
|
|
153
|
+
- Updated TypeScript plugin exports and dependencies: added @push.rocks/smartrust and exported smartrust in ts/plugins.ts.
|
|
154
|
+
- Large README overhaul to reflect rebranding, install instructions, architecture and legal/company info.
|
|
155
|
+
|
|
156
|
+
## 2026-02-10 - 1.3.1 - fix(deps)
|
|
157
|
+
add workspace dependency entries for multiple crates across mailer-bin, mailer-core, and mailer-security
|
|
158
|
+
|
|
159
|
+
- rust/crates/mailer-bin/Cargo.toml: add clap.workspace = true
|
|
160
|
+
- rust/crates/mailer-core/Cargo.toml: add regex.workspace = true, base64.workspace = true, uuid.workspace = true
|
|
161
|
+
- rust/crates/mailer-security/Cargo.toml: add serde_json.workspace = true, tokio.workspace = true, hickory-resolver.workspace = true, ipnet.workspace = true, rustls-pki-types.workspace = true, psl.workspace = true
|
|
162
|
+
- Purpose: align and enable workspace-managed dependencies for the affected crates
|
|
163
|
+
|
|
164
|
+
## 2026-02-10 - 1.3.0 - feat(mail/delivery)
|
|
165
|
+
add error-count based blocking to rate limiter; improve test SMTP server port selection; add tsbuild scripts and devDependency; remove stale backup file
|
|
166
|
+
|
|
167
|
+
- Add TokenBucket error tracking (errors, firstErrorTime) and initialize fields for global and per-key buckets
|
|
168
|
+
- Introduce RateLimiter.recordError(key, window, threshold) to track errors and decide blocking when threshold exceeded
|
|
169
|
+
- Update test SMTP server loader to dynamically find a free port using smartnetwork and add a recordError stub to the mock server
|
|
170
|
+
- Add build and check scripts to package.json and add @git.zone/tsbuild to devDependencies
|
|
171
|
+
- Remove a large backup file (classes.emailsendjob.ts.backup) from the repo; minor whitespace and logging cleanups in SMTP server code
|
|
172
|
+
|
|
173
|
+
## 2025-10-24 - 1.2.1 - fix(mail/delivery)
|
|
174
|
+
Centralize runtime/plugin imports and switch modules to use plugins exports; unify EventEmitter usage; update Deno dependencies and small path/server refactors
|
|
175
|
+
|
|
176
|
+
- Centralized Node and third-party imports in ts/plugins.ts and re-exported commonly used utilities (net, tls, dns, fs, smartfile, smartdns, smartmail, mailauth, uuid, ip, LRUCache, etc).
|
|
177
|
+
- Replaced direct EventEmitter / Node built-in imports with plugins.EventEmitter across delivery, smtpclient, routing and the unified email server to standardize runtime integration.
|
|
178
|
+
- Updated deno.json dependency map: added @push.rocks/smartfile, @push.rocks/smartdns, @tsclass/tsclass and ip; reordered lru-cache entry.
|
|
179
|
+
- Stopped exporting ./dns/index.ts from ts/index.ts (DNS is available via mail/routing) to avoid duplicate exports.
|
|
180
|
+
- Added keysDir alias and dnsRecordsDir in ts/paths.ts and small path-related fixes.
|
|
181
|
+
- Added a placeholder SmtpServer and other minor delivery/smtpserver refactors and sanitizations.
|
|
182
|
+
- Added a local .claude/settings.local.json for development permissions (local-only configuration).
|
|
183
|
+
|
|
184
|
+
## 2025-10-24 - 1.2.0 - feat(plugins)
|
|
185
|
+
Add smartmail, mailauth and uuid to Deno dependencies and export them from plugins; include local dev permissions file
|
|
186
|
+
|
|
187
|
+
- Add @push.rocks/smartmail, mailauth and uuid to deno.json dependencies so email parsing, DKIM and UUID utilities are available.
|
|
188
|
+
- Export smartmail, mailauth and uuid from ts/plugins.ts for centralized access across the codebase.
|
|
189
|
+
- Add .claude/settings.local.json to define local development permissions (tooling/dev environment configuration).
|
|
190
|
+
|
|
191
|
+
## 2025-10-24 - 1.1.0 - feat(ci)
|
|
192
|
+
Add CI, release and npm-publish automation; introduce release template and local settings
|
|
193
|
+
|
|
194
|
+
- Add CI workflow (.gitea/workflows/ci.yml) to run TypeScript checks, linting, formatting and platform compilation tests
|
|
195
|
+
- Add release workflow (.gitea/workflows/release.yml) to compile binaries for multiple platforms, generate checksums, create/update Gitea releases and upload assets
|
|
196
|
+
- Add npm publish workflow (.gitea/workflows/npm-publish.yml) to build package from a tag and publish precompiled binaries to npm (tag-triggered)
|
|
197
|
+
- Add a Gitea release template (.gitea/release-template.md) describing platforms, checksums and installation options
|
|
198
|
+
- Add local tool permission settings (.claude/settings.local.json) used by local tooling
|
|
199
|
+
- No API or runtime source changes — this commit is focused on CI/CD, release automation and packaging
|
|
200
|
+
|
|
201
|
+
## 2025-10-24 - 1.0.1 - fix(dev)
|
|
202
|
+
Add local development settings file to grant tooling permissions
|
|
203
|
+
|
|
204
|
+
- Add a local development settings file to configure permissions used by repository tooling (file-system reads and a small set of shell/CLI operations).
|
|
205
|
+
- This is a developer-only configuration — it doesn't change runtime code or published artifacts.
|
|
206
|
+
- No functional changes to the mailer library; bumps patch version only to record the repository config change.
|
|
207
|
+
|
|
208
|
+
## 1.0.0 (2025-10-24)
|
|
209
|
+
|
|
210
|
+
### Features
|
|
211
|
+
|
|
212
|
+
- Initial release of @serve.zone/mailer
|
|
213
|
+
- SMTP server and client implementation
|
|
214
|
+
- HTTP REST API (Mailgun-compatible)
|
|
215
|
+
- Automatic DNS management via Cloudflare
|
|
216
|
+
- Systemd daemon service
|
|
217
|
+
- CLI interface for all operations
|
|
218
|
+
- DKIM signing and SPF validation
|
|
219
|
+
- Email routing and delivery queue
|
|
220
|
+
- Rate limiting and bounce management
|
|
Binary file
|
|
Binary file
|
package/license
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Task Venture Capital GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@push.rocks/smartmta",
|
|
3
|
+
"version": "5.1.1",
|
|
4
|
+
"description": "A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mta",
|
|
7
|
+
"smtp",
|
|
8
|
+
"email",
|
|
9
|
+
"mail server",
|
|
10
|
+
"mail transfer agent",
|
|
11
|
+
"dkim",
|
|
12
|
+
"spf",
|
|
13
|
+
"dmarc",
|
|
14
|
+
"dns",
|
|
15
|
+
"cloudflare",
|
|
16
|
+
"typescript",
|
|
17
|
+
"rust"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://code.foss.global/push.rocks/smartmta",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://code.foss.global/push.rocks/smartmta/issues"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://code.foss.global/push.rocks/smartmta.git"
|
|
26
|
+
},
|
|
27
|
+
"author": "Task Venture Capital GmbH",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"mailer": "./bin/mailer-wrapper.js"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "tstest test/ --logfile --verbose --timeout 60",
|
|
35
|
+
"build": "tsbuild tsfolders && tsrust",
|
|
36
|
+
"check": "tsbuild check"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@git.zone/tsbuild": "^4.1.2",
|
|
40
|
+
"@git.zone/tsrust": "^1.3.0",
|
|
41
|
+
"@git.zone/tstest": "^3.1.8",
|
|
42
|
+
"@types/mailparser": "^3.4.6",
|
|
43
|
+
"@types/node": "^25.2.3",
|
|
44
|
+
"tsx": "^4.21.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@push.rocks/smartfile": "^13.1.2",
|
|
48
|
+
"@push.rocks/smartfs": "^1.3.1",
|
|
49
|
+
"@push.rocks/smartlog": "^3.1.8",
|
|
50
|
+
"@push.rocks/smartmail": "^2.2.0",
|
|
51
|
+
"@push.rocks/smartpath": "^6.0.0",
|
|
52
|
+
"@push.rocks/smartrust": "^1.2.0",
|
|
53
|
+
"@tsclass/tsclass": "^9.2.0",
|
|
54
|
+
"lru-cache": "^11.2.5",
|
|
55
|
+
"mailparser": "^3.9.3",
|
|
56
|
+
"uuid": "^13.0.0"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"bin/",
|
|
60
|
+
"scripts/install-binary.js",
|
|
61
|
+
"dist_rust/**/*",
|
|
62
|
+
"readme.md",
|
|
63
|
+
"license",
|
|
64
|
+
"changelog.md"
|
|
65
|
+
],
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=14.0.0"
|
|
68
|
+
},
|
|
69
|
+
"os": [
|
|
70
|
+
"darwin",
|
|
71
|
+
"linux",
|
|
72
|
+
"win32"
|
|
73
|
+
],
|
|
74
|
+
"cpu": [
|
|
75
|
+
"x64",
|
|
76
|
+
"arm64"
|
|
77
|
+
],
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public",
|
|
80
|
+
"registry": "https://registry.npmjs.org/"
|
|
81
|
+
},
|
|
82
|
+
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
|
83
|
+
}
|
package/readme.hints.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Project Hints
|
|
2
|
+
|
|
3
|
+
## Rust Workspace
|
|
4
|
+
- Rust code lives in `rust/` with a Cargo workspace
|
|
5
|
+
- Crates: `mailer-core`, `mailer-smtp`, `mailer-security`, `mailer-napi`, `mailer-bin`
|
|
6
|
+
- `mailer-bin` is the binary target that `tsrust` builds for cross-compilation
|
|
7
|
+
- `mailer-napi` is a cdylib for N-API bindings (not built by tsrust, needs separate napi-rs build pipeline)
|
|
8
|
+
- tsrust only supports binary targets (looks for `src/main.rs` or `[[bin]]` entries)
|
|
9
|
+
- Cross-compilation targets: `linux_amd64`, `linux_arm64` (configured in `npmextra.json`)
|
|
10
|
+
- Build output goes to `dist_rust/`
|
|
11
|
+
|
|
12
|
+
## Build
|
|
13
|
+
- `pnpm build` runs `tsbuild tsfolders && tsrust`
|
|
14
|
+
- Note: `tsbuild tsfolders` requires a `tsconfig.json` at the project root (currently missing - pre-existing issue)
|
|
15
|
+
- `tsrust` independently works and produces binaries in `dist_rust/`
|
|
16
|
+
|
|
17
|
+
## Key Dependencies (Rust)
|
|
18
|
+
- `tokio` - async runtime
|
|
19
|
+
- `tokio-rustls` - TLS
|
|
20
|
+
- `hickory-dns` (hickory-resolver) - DNS resolution
|
|
21
|
+
- `mail-auth` - DKIM/SPF/DMARC (by Stalwart Labs)
|
|
22
|
+
- `mailparse` - MIME parsing
|
|
23
|
+
- `napi` + `napi-derive` - Node.js bindings
|
|
24
|
+
- `ring` - crypto primitives
|
|
25
|
+
- `dashmap` - concurrent hash maps
|
package/readme.md
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# @push.rocks/smartmta
|
|
2
|
+
|
|
3
|
+
A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with a Rust-powered SMTP engine — no nodemailer, no shortcuts. 🚀
|
|
4
|
+
|
|
5
|
+
## Issue Reporting and Security
|
|
6
|
+
|
|
7
|
+
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm install @push.rocks/smartmta
|
|
13
|
+
# or
|
|
14
|
+
npm install @push.rocks/smartmta
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After installation, run `pnpm build` to compile the Rust binary (`mailer-bin`). The Rust binary is **required** — `smartmta` will not start without it.
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
`@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. The SMTP engine runs as a Rust binary for maximum performance, communicating with the TypeScript orchestration layer via JSON-over-stdin/stdout IPC.
|
|
22
|
+
|
|
23
|
+
### ⚡ What's Inside
|
|
24
|
+
|
|
25
|
+
| Module | What It Does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **Rust SMTP Server** | High-performance SMTP engine in Rust — TCP/TLS listener, STARTTLS, AUTH, pipelining, per-connection rate limiting |
|
|
28
|
+
| **Rust SMTP Client** | Outbound delivery with connection pooling, retry logic, TLS negotiation, DKIM signing — all in Rust |
|
|
29
|
+
| **DKIM** | Key generation, signing, and verification — per domain, with automatic rotation |
|
|
30
|
+
| **SPF** | Full SPF record validation via Rust |
|
|
31
|
+
| **DMARC** | Policy enforcement and verification |
|
|
32
|
+
| **Email Router** | Pattern-based routing with priority, forward/deliver/reject/process actions |
|
|
33
|
+
| **Bounce Manager** | Automatic bounce detection via Rust, classification (hard/soft), and suppression tracking |
|
|
34
|
+
| **Content Scanner** | Spam, phishing, malware, XSS, and suspicious link detection — powered by Rust |
|
|
35
|
+
| **IP Reputation** | DNSBL checks, proxy/TOR/VPN detection, risk scoring via Rust |
|
|
36
|
+
| **Rate Limiter** | Hierarchical rate limiting (global, per-domain, per-IP) |
|
|
37
|
+
| **Delivery Queue** | Persistent queue with exponential backoff retry |
|
|
38
|
+
| **Template Engine** | Email templates with variable substitution |
|
|
39
|
+
| **Domain Registry** | Multi-domain management with per-domain configuration |
|
|
40
|
+
| **DNS Manager** | Automatic DNS record management (MX, SPF, DKIM, DMARC) |
|
|
41
|
+
|
|
42
|
+
### 🏗️ Architecture
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
46
|
+
│ UnifiedEmailServer │
|
|
47
|
+
│ (orchestrates all components, emits events) │
|
|
48
|
+
├───────────┬───────────┬──────────────┬───────────────────────┤
|
|
49
|
+
│ Email │ Security │ Delivery │ Configuration │
|
|
50
|
+
│ Router │ Stack │ System │ │
|
|
51
|
+
│ ┌──────┐ │ ┌───────┐ │ ┌──────────┐ │ ┌────────────────┐ │
|
|
52
|
+
│ │Match │ │ │ DKIM │ │ │ Queue │ │ │ DomainRegistry │ │
|
|
53
|
+
│ │Route │ │ │ SPF │ │ │ Rate Lim │ │ │ DnsManager │ │
|
|
54
|
+
│ │ Act │ │ │ DMARC │ │ │ Retry │ │ │ DKIMCreator │ │
|
|
55
|
+
│ └──────┘ │ │ IPRep │ │ └──────────┘ │ │ Templates │ │
|
|
56
|
+
│ │ │ Scan │ │ │ └────────────────┘ │
|
|
57
|
+
│ │ └───────┘ │ │ │
|
|
58
|
+
├───────────┴───────────┴──────────────┴───────────────────────┤
|
|
59
|
+
│ Rust Security Bridge (smartrust IPC) │
|
|
60
|
+
├──────────────────────────────────────────────────────────────┤
|
|
61
|
+
│ Rust Acceleration Layer │
|
|
62
|
+
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────┐ │
|
|
63
|
+
│ │ mailer-smtp │ │mailer-security│ │ mailer-core │ │
|
|
64
|
+
│ │ SMTP Server │ │DKIM/SPF/DMARC │ │ Types/Validation │ │
|
|
65
|
+
│ │ SMTP Client │ │IP Rep/Content │ │ MIME/Bounce │ │
|
|
66
|
+
│ │ TLS/AUTH │ │ Scanning │ │ Detection │ │
|
|
67
|
+
│ └──────────────┘ └───────────────┘ └──────────────────┘ │
|
|
68
|
+
└──────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Data flow for inbound mail:**
|
|
72
|
+
|
|
73
|
+
1. 📨 Rust SMTP server accepts the connection and handles the full SMTP protocol
|
|
74
|
+
2. 🔒 On `DATA` completion, Rust runs the security pipeline **in-process** (DKIM/SPF/DMARC verification, content scanning, IP reputation check) — zero IPC round-trips
|
|
75
|
+
3. 📤 Rust emits an `emailReceived` event via IPC with pre-computed security results attached
|
|
76
|
+
4. 🔀 TypeScript processes the email (routing decisions using the pre-computed results, delivery)
|
|
77
|
+
5. ✅ Rust sends the final SMTP response to the client
|
|
78
|
+
|
|
79
|
+
**Data flow for outbound mail:**
|
|
80
|
+
|
|
81
|
+
1. 📝 TypeScript constructs the email and resolves DKIM keys for the sender domain
|
|
82
|
+
2. 🦀 Sends to Rust via IPC — Rust builds the RFC 2822 message, signs with DKIM, and delivers via its SMTP client with connection pooling
|
|
83
|
+
3. 📬 Result (accepted/rejected recipients, server response) returned to TypeScript
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### 🚀 Setting Up the Email Server
|
|
88
|
+
|
|
89
|
+
The central entry point is `UnifiedEmailServer`, which orchestrates the Rust SMTP server, routing, security, and delivery:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { UnifiedEmailServer } from '@push.rocks/smartmta';
|
|
93
|
+
|
|
94
|
+
const emailServer = new UnifiedEmailServer(dcRouterRef, {
|
|
95
|
+
// Ports to listen on (465 = implicit TLS, 25/587 = STARTTLS)
|
|
96
|
+
ports: [25, 587, 465],
|
|
97
|
+
hostname: 'mail.example.com',
|
|
98
|
+
|
|
99
|
+
// Multi-domain configuration
|
|
100
|
+
domains: [
|
|
101
|
+
{
|
|
102
|
+
domain: 'example.com',
|
|
103
|
+
dnsMode: 'external-dns',
|
|
104
|
+
dkim: {
|
|
105
|
+
selector: 'default',
|
|
106
|
+
keySize: 2048,
|
|
107
|
+
rotateKeys: true,
|
|
108
|
+
rotationInterval: 90,
|
|
109
|
+
},
|
|
110
|
+
rateLimits: {
|
|
111
|
+
outbound: { messagesPerMinute: 100 },
|
|
112
|
+
inbound: { messagesPerMinute: 200, connectionsPerIp: 20 },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
|
|
117
|
+
// Routing rules (evaluated by priority, highest first)
|
|
118
|
+
routes: [
|
|
119
|
+
{
|
|
120
|
+
name: 'catch-all-forward',
|
|
121
|
+
priority: 10,
|
|
122
|
+
match: {
|
|
123
|
+
recipients: '*@example.com',
|
|
124
|
+
},
|
|
125
|
+
action: {
|
|
126
|
+
type: 'forward',
|
|
127
|
+
forward: {
|
|
128
|
+
host: 'internal-mail.example.com',
|
|
129
|
+
port: 25,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'reject-spam-senders',
|
|
135
|
+
priority: 100,
|
|
136
|
+
match: {
|
|
137
|
+
senders: '*@spamdomain.com',
|
|
138
|
+
},
|
|
139
|
+
action: {
|
|
140
|
+
type: 'reject',
|
|
141
|
+
reject: {
|
|
142
|
+
code: 550,
|
|
143
|
+
message: 'Sender rejected by policy',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
|
|
149
|
+
// Authentication settings for the SMTP server
|
|
150
|
+
auth: {
|
|
151
|
+
required: false,
|
|
152
|
+
methods: ['PLAIN', 'LOGIN'],
|
|
153
|
+
users: [{ username: 'outbound', password: 'secret' }],
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// TLS certificates
|
|
157
|
+
tls: {
|
|
158
|
+
certPath: '/etc/ssl/mail.crt',
|
|
159
|
+
keyPath: '/etc/ssl/mail.key',
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
maxMessageSize: 25 * 1024 * 1024, // 25 MB
|
|
163
|
+
maxClients: 500,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// start() boots the Rust SMTP server, security bridge, DNS records, and delivery queue
|
|
167
|
+
await emailServer.start();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
> 🔒 **Note:** `start()` will throw if the Rust binary is not compiled. Run `pnpm build` first.
|
|
171
|
+
|
|
172
|
+
### 📧 Sending Outbound Emails
|
|
173
|
+
|
|
174
|
+
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:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { Email, UnifiedEmailServer } from '@push.rocks/smartmta';
|
|
178
|
+
|
|
179
|
+
// Build an email
|
|
180
|
+
const email = new Email({
|
|
181
|
+
from: 'sender@example.com',
|
|
182
|
+
to: ['recipient@example.com'],
|
|
183
|
+
cc: ['cc@example.com'],
|
|
184
|
+
subject: 'Hello from smartmta! 🚀',
|
|
185
|
+
text: 'Plain text body',
|
|
186
|
+
html: '<h1>Hello!</h1><p>HTML body with <strong>formatting</strong></p>',
|
|
187
|
+
priority: 'high',
|
|
188
|
+
attachments: [
|
|
189
|
+
{
|
|
190
|
+
filename: 'report.pdf',
|
|
191
|
+
content: pdfBuffer,
|
|
192
|
+
contentType: 'application/pdf',
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Send via the Rust SMTP client (connection pooling, TLS, DKIM signing)
|
|
198
|
+
const result = await emailServer.sendOutboundEmail('smtp.example.com', 587, email, {
|
|
199
|
+
auth: { user: 'sender@example.com', pass: 'your-password' },
|
|
200
|
+
dkimDomain: 'example.com',
|
|
201
|
+
dkimSelector: 'default',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
console.log(`Accepted: ${result.accepted.join(', ')}`);
|
|
205
|
+
console.log(`Response: ${result.response}`);
|
|
206
|
+
// -> Accepted: recipient@example.com
|
|
207
|
+
// -> Response: 2.0.0 Ok: queued
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The `sendOutboundEmail` method:
|
|
211
|
+
- 🔑 Automatically resolves DKIM keys from the `DKIMCreator` for the specified domain
|
|
212
|
+
- 🔗 Uses connection pooling in Rust — reuses TCP/TLS connections across sends
|
|
213
|
+
- ⏱️ Configurable connection and socket timeouts via `outbound` options on the server
|
|
214
|
+
|
|
215
|
+
### 🔑 DKIM Signing & Key Management
|
|
216
|
+
|
|
217
|
+
DKIM key management is handled by `DKIMCreator`, which generates, stores, and rotates keys per domain. Signing is performed automatically by the Rust SMTP client during outbound delivery:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { DKIMCreator } from '@push.rocks/smartmta';
|
|
221
|
+
|
|
222
|
+
const dkimCreator = new DKIMCreator('/path/to/keys', storageManager);
|
|
223
|
+
|
|
224
|
+
// Auto-generate keys if they don't exist
|
|
225
|
+
await dkimCreator.handleDKIMKeysForDomain('example.com');
|
|
226
|
+
|
|
227
|
+
// Get the DNS record you need to publish
|
|
228
|
+
const dnsRecord = await dkimCreator.getDNSRecordForDomain('example.com');
|
|
229
|
+
console.log(dnsRecord);
|
|
230
|
+
// -> { type: 'TXT', name: 'default._domainkey.example.com', value: 'v=DKIM1; k=rsa; p=...' }
|
|
231
|
+
|
|
232
|
+
// Check if keys need rotation
|
|
233
|
+
const needsRotation = await dkimCreator.needsRotation('example.com', 'default', 90);
|
|
234
|
+
if (needsRotation) {
|
|
235
|
+
const newSelector = await dkimCreator.rotateDkimKeys('example.com', 'default', 2048);
|
|
236
|
+
console.log(`Rotated to selector: ${newSelector}`);
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
When `UnifiedEmailServer.start()` is called:
|
|
241
|
+
- DKIM keys are generated or loaded for every configured domain
|
|
242
|
+
- Signing is applied to all outbound mail via the Rust security bridge
|
|
243
|
+
- Key rotation is checked automatically based on your `rotationInterval` config
|
|
244
|
+
|
|
245
|
+
### 🛡️ Email Authentication (SPF, DKIM, DMARC)
|
|
246
|
+
|
|
247
|
+
All verification is powered by the Rust binary. For inbound mail, `UnifiedEmailServer` runs the full security pipeline **automatically** — DKIM, SPF, DMARC, content scanning, and IP reputation in a single Rust pass. Results are attached as headers (`Received-SPF`, `X-DKIM-Result`, `X-DMARC-Result`).
|
|
248
|
+
|
|
249
|
+
You can also use the individual verifiers directly:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { DKIMVerifier, SpfVerifier, DmarcVerifier } from '@push.rocks/smartmta';
|
|
253
|
+
|
|
254
|
+
// SPF verification
|
|
255
|
+
const spfVerifier = new SpfVerifier();
|
|
256
|
+
const spfResult = await spfVerifier.verify(email, senderIP, heloDomain);
|
|
257
|
+
// -> { result: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none', domain, ip }
|
|
258
|
+
|
|
259
|
+
// DKIM verification
|
|
260
|
+
const dkimVerifier = new DKIMVerifier();
|
|
261
|
+
const dkimResult = await dkimVerifier.verify(rawEmailContent);
|
|
262
|
+
// -> [{ is_valid: true, domain: 'example.com', selector: 'default', status: 'pass' }]
|
|
263
|
+
|
|
264
|
+
// DMARC verification
|
|
265
|
+
const dmarcVerifier = new DmarcVerifier();
|
|
266
|
+
const dmarcResult = await dmarcVerifier.verify(email, spfResult, dkimResult);
|
|
267
|
+
// -> { action: 'pass' | 'quarantine' | 'reject', policy, spfDomainAligned, dkimDomainAligned }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 🔀 Email Routing
|
|
271
|
+
|
|
272
|
+
Pattern-based routing engine with priority ordering and flexible match criteria. Routes are evaluated by priority (highest first):
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { EmailRouter } from '@push.rocks/smartmta';
|
|
276
|
+
|
|
277
|
+
const router = new EmailRouter([
|
|
278
|
+
{
|
|
279
|
+
name: 'admin-mail',
|
|
280
|
+
priority: 100,
|
|
281
|
+
match: {
|
|
282
|
+
recipients: 'admin@example.com',
|
|
283
|
+
authenticated: true,
|
|
284
|
+
},
|
|
285
|
+
action: {
|
|
286
|
+
type: 'deliver',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: 'external-forward',
|
|
291
|
+
priority: 50,
|
|
292
|
+
match: {
|
|
293
|
+
recipients: '*@example.com',
|
|
294
|
+
sizeRange: { max: 10 * 1024 * 1024 }, // under 10MB
|
|
295
|
+
},
|
|
296
|
+
action: {
|
|
297
|
+
type: 'forward',
|
|
298
|
+
forward: {
|
|
299
|
+
host: 'backend-mail.internal',
|
|
300
|
+
port: 25,
|
|
301
|
+
preserveHeaders: true,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'process-with-scanning',
|
|
307
|
+
priority: 10,
|
|
308
|
+
match: {
|
|
309
|
+
recipients: '*@*',
|
|
310
|
+
},
|
|
311
|
+
action: {
|
|
312
|
+
type: 'process',
|
|
313
|
+
process: {
|
|
314
|
+
scan: true,
|
|
315
|
+
dkim: true,
|
|
316
|
+
queue: 'normal',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
// Evaluate routes against an email context
|
|
323
|
+
const matchedRoute = await router.evaluateRoutes(emailContext);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Route Action Types
|
|
327
|
+
|
|
328
|
+
| Action | Description |
|
|
329
|
+
|---|---|
|
|
330
|
+
| `forward` | Forward the email to another SMTP server via the Rust SMTP client |
|
|
331
|
+
| `deliver` | Queue for local MTA delivery |
|
|
332
|
+
| `process` | Queue for processing (with optional content scanning and DKIM signing) |
|
|
333
|
+
| `reject` | Reject with a configurable SMTP error code and message |
|
|
334
|
+
|
|
335
|
+
#### Match Criteria
|
|
336
|
+
|
|
337
|
+
| Criterion | Description |
|
|
338
|
+
|---|---|
|
|
339
|
+
| `recipients` | Glob patterns for recipient addresses (`*@example.com`) |
|
|
340
|
+
| `senders` | Glob patterns for sender addresses |
|
|
341
|
+
| `clientIp` | IP addresses or CIDR ranges |
|
|
342
|
+
| `authenticated` | Require authentication status |
|
|
343
|
+
| `headers` | Match specific headers (string or RegExp) |
|
|
344
|
+
| `sizeRange` | Message size constraints (`{ min?, max? }`) |
|
|
345
|
+
| `subject` | Subject line pattern (string or RegExp) |
|
|
346
|
+
| `hasAttachments` | Filter by attachment presence |
|
|
347
|
+
|
|
348
|
+
### ⏱️ Rate Limiting
|
|
349
|
+
|
|
350
|
+
Hierarchical rate limiting to protect your server and maintain deliverability:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { Delivery } from '@push.rocks/smartmta';
|
|
354
|
+
const { UnifiedRateLimiter } = Delivery;
|
|
355
|
+
|
|
356
|
+
const rateLimiter = new UnifiedRateLimiter({
|
|
357
|
+
global: {
|
|
358
|
+
maxMessagesPerMinute: 1000,
|
|
359
|
+
maxRecipientsPerMessage: 500,
|
|
360
|
+
maxConnectionsPerIP: 20,
|
|
361
|
+
maxErrorsPerIP: 10,
|
|
362
|
+
maxAuthFailuresPerIP: 5,
|
|
363
|
+
blockDuration: 600000, // 10 minutes
|
|
364
|
+
},
|
|
365
|
+
domains: {
|
|
366
|
+
'example.com': {
|
|
367
|
+
maxMessagesPerMinute: 100,
|
|
368
|
+
maxRecipientsPerMessage: 50,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Check before sending
|
|
374
|
+
const allowed = rateLimiter.checkMessageLimit(
|
|
375
|
+
'sender@example.com',
|
|
376
|
+
'192.168.1.1',
|
|
377
|
+
recipientCount,
|
|
378
|
+
undefined,
|
|
379
|
+
'example.com'
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
if (!allowed.allowed) {
|
|
383
|
+
console.log(`Rate limited: ${allowed.reason}`);
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 📬 Bounce Management
|
|
388
|
+
|
|
389
|
+
Automatic bounce detection (via Rust), classification, and suppression tracking:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { Core } from '@push.rocks/smartmta';
|
|
393
|
+
const { BounceManager } = Core;
|
|
394
|
+
|
|
395
|
+
const bounceManager = new BounceManager();
|
|
396
|
+
|
|
397
|
+
// Process an SMTP failure
|
|
398
|
+
const bounce = await bounceManager.processSmtpFailure(
|
|
399
|
+
'recipient@example.com',
|
|
400
|
+
'550 5.1.1 User unknown',
|
|
401
|
+
{ originalEmailId: 'msg-123' }
|
|
402
|
+
);
|
|
403
|
+
// -> { bounceType: 'invalid_recipient', bounceCategory: 'hard', ... }
|
|
404
|
+
|
|
405
|
+
// Check if an address is suppressed due to bounces
|
|
406
|
+
const suppressed = bounceManager.isEmailSuppressed('recipient@example.com');
|
|
407
|
+
|
|
408
|
+
// Manage the suppression list
|
|
409
|
+
bounceManager.addToSuppressionList('bad@example.com', 'repeated hard bounces');
|
|
410
|
+
bounceManager.removeFromSuppressionList('recovered@example.com');
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 📝 Email Templates
|
|
414
|
+
|
|
415
|
+
Template engine with variable substitution for transactional and notification emails:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { Core } from '@push.rocks/smartmta';
|
|
419
|
+
const { TemplateManager } = Core;
|
|
420
|
+
|
|
421
|
+
const templates = new TemplateManager({
|
|
422
|
+
from: 'noreply@example.com',
|
|
423
|
+
footerHtml: '<p>© 2026 Example Corp</p>',
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Register a template
|
|
427
|
+
templates.registerTemplate({
|
|
428
|
+
id: 'welcome',
|
|
429
|
+
name: 'Welcome Email',
|
|
430
|
+
description: 'Sent to new users',
|
|
431
|
+
from: 'welcome@example.com',
|
|
432
|
+
subject: 'Welcome, {{name}}!',
|
|
433
|
+
bodyHtml: '<h1>Welcome, {{name}}!</h1><p>Your account is ready.</p>',
|
|
434
|
+
bodyText: 'Welcome, {{name}}! Your account is ready.',
|
|
435
|
+
category: 'transactional',
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Create an Email object from the template
|
|
439
|
+
const email = await templates.createEmail('welcome', {
|
|
440
|
+
to: 'newuser@example.com',
|
|
441
|
+
variables: { name: 'Alice' },
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 🌍 DNS Management
|
|
446
|
+
|
|
447
|
+
When `UnifiedEmailServer.start()` is called, it automatically ensures MX, SPF, DKIM, and DMARC records are in place for all configured domains:
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
const emailServer = new UnifiedEmailServer(dcRouterRef, {
|
|
451
|
+
hostname: 'mail.example.com',
|
|
452
|
+
domains: [
|
|
453
|
+
{
|
|
454
|
+
domain: 'example.com',
|
|
455
|
+
dnsMode: 'external-dns', // managed via Cloudflare API
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
// ... other config
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// DNS records are set up automatically on start:
|
|
462
|
+
// - MX records pointing to your mail server
|
|
463
|
+
// - SPF TXT records authorizing your server IP
|
|
464
|
+
// - DKIM TXT records with public keys from DKIMCreator
|
|
465
|
+
// - DMARC TXT records with your policy
|
|
466
|
+
await emailServer.start();
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## 🦀 Rust Acceleration Layer
|
|
470
|
+
|
|
471
|
+
Performance-critical operations are implemented in Rust and communicate with the TypeScript runtime via `@push.rocks/smartrust` (JSON-over-stdin/stdout IPC). The Rust workspace lives at `rust/` with four crates:
|
|
472
|
+
|
|
473
|
+
| Crate | Status | Purpose |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `mailer-core` | ✅ Complete (26 tests) | Email types, validation, MIME building, bounce detection |
|
|
476
|
+
| `mailer-security` | ✅ Complete (22 tests) | DKIM sign/verify, SPF, DMARC, IP reputation/DNSBL, content scanning |
|
|
477
|
+
| `mailer-smtp` | ✅ Complete (106 tests) | Full SMTP protocol engine — TCP/TLS server + client, STARTTLS, AUTH, pipelining, connection pooling, in-process security pipeline |
|
|
478
|
+
| `mailer-bin` | ✅ Complete | CLI + smartrust IPC bridge — wires everything together |
|
|
479
|
+
|
|
480
|
+
### What Runs Where
|
|
481
|
+
|
|
482
|
+
| Operation | Runs In | Why |
|
|
483
|
+
|---|---|---|
|
|
484
|
+
| SMTP server (port listening, protocol, TLS) | 🦀 Rust | Performance, memory safety, zero-copy parsing |
|
|
485
|
+
| SMTP client (outbound delivery, connection pooling) | 🦀 Rust | Connection management, TLS negotiation |
|
|
486
|
+
| DKIM signing & verification | 🦀 Rust | Crypto-heavy, benefits from native speed |
|
|
487
|
+
| SPF validation | 🦀 Rust | DNS lookups with async resolver |
|
|
488
|
+
| DMARC policy checking | 🦀 Rust | Integrates with SPF/DKIM results |
|
|
489
|
+
| IP reputation / DNSBL | 🦀 Rust | Parallel DNS queries |
|
|
490
|
+
| Content scanning (text patterns) | 🦀 Rust | Regex engine performance |
|
|
491
|
+
| Bounce detection (pattern matching) | 🦀 Rust | Regex engine performance |
|
|
492
|
+
| Email validation & MIME building | 🦀 Rust | Parsing performance |
|
|
493
|
+
| Email routing & orchestration | 🟦 TypeScript | Business logic, flexibility |
|
|
494
|
+
| Delivery queue & retry | 🟦 TypeScript | State management, persistence |
|
|
495
|
+
| Template rendering | 🟦 TypeScript | String interpolation |
|
|
496
|
+
| Domain & DNS management | 🟦 TypeScript | API integrations |
|
|
497
|
+
|
|
498
|
+
## 📁 Project Structure
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
smartmta/
|
|
502
|
+
├── ts/ # TypeScript source
|
|
503
|
+
│ ├── mail/
|
|
504
|
+
│ │ ├── core/ # Email, EmailValidator, BounceManager, TemplateManager
|
|
505
|
+
│ │ ├── delivery/ # DeliveryQueue, DeliverySystem, RateLimiter
|
|
506
|
+
│ │ ├── routing/ # UnifiedEmailServer, EmailRouter, DomainRegistry, DnsManager
|
|
507
|
+
│ │ └── security/ # DKIMCreator, DKIMVerifier, SpfVerifier, DmarcVerifier
|
|
508
|
+
│ └── security/ # ContentScanner, IPReputationChecker, RustSecurityBridge
|
|
509
|
+
├── rust/ # Rust workspace
|
|
510
|
+
│ └── crates/
|
|
511
|
+
│ ├── mailer-core/ # Email types, validation, MIME, bounce detection
|
|
512
|
+
│ ├── mailer-security/ # DKIM, SPF, DMARC, IP reputation, content scanning
|
|
513
|
+
│ ├── mailer-smtp/ # Full SMTP server + client (TCP/TLS, rate limiting, pooling)
|
|
514
|
+
│ └── mailer-bin/ # CLI + smartrust IPC bridge
|
|
515
|
+
├── test/ # Test suite (116 TypeScript + 154 Rust tests)
|
|
516
|
+
├── dist_ts/ # Compiled TypeScript output
|
|
517
|
+
└── dist_rust/ # Compiled Rust binaries
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## 🧪 Testing
|
|
521
|
+
|
|
522
|
+
The project has comprehensive test coverage with both unit and end-to-end tests:
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
# Build Rust binary first
|
|
526
|
+
pnpm build
|
|
527
|
+
|
|
528
|
+
# Run all tests
|
|
529
|
+
pnpm test
|
|
530
|
+
|
|
531
|
+
# Run specific test files
|
|
532
|
+
tstest test/test.e2e.server-lifecycle.node.ts --verbose --timeout 60
|
|
533
|
+
tstest test/test.e2e.inbound-smtp.node.ts --verbose --timeout 60
|
|
534
|
+
tstest test/test.e2e.routing-actions.node.ts --verbose --timeout 60
|
|
535
|
+
tstest test/test.e2e.outbound-delivery.node.ts --verbose --timeout 60
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**E2E tests** exercise the full pipeline — starting `UnifiedEmailServer`, connecting via raw TCP sockets, sending SMTP transactions, verifying routing actions, and testing outbound delivery through a mock SMTP receiver.
|
|
539
|
+
|
|
540
|
+
## API Reference
|
|
541
|
+
|
|
542
|
+
### Exported Classes (top-level)
|
|
543
|
+
|
|
544
|
+
| Class | Description |
|
|
545
|
+
|---|---|
|
|
546
|
+
| `UnifiedEmailServer` | 🎯 Main entry point — orchestrates SMTP server, routing, security, and delivery |
|
|
547
|
+
| `Email` | Email message class with validation, attachments, headers, and RFC 822 serialization |
|
|
548
|
+
| `EmailRouter` | Pattern-based route matching and evaluation engine |
|
|
549
|
+
| `DomainRegistry` | Multi-domain configuration manager |
|
|
550
|
+
| `DnsManager` | Automatic DNS record management |
|
|
551
|
+
| `DKIMCreator` | DKIM key generation, storage, rotation |
|
|
552
|
+
| `DKIMVerifier` | DKIM signature verification (delegates to Rust) |
|
|
553
|
+
| `SpfVerifier` | SPF record validation (delegates to Rust) |
|
|
554
|
+
| `DmarcVerifier` | DMARC policy enforcement (delegates to Rust) |
|
|
555
|
+
|
|
556
|
+
### Namespaced Exports
|
|
557
|
+
|
|
558
|
+
| Namespace | Classes |
|
|
559
|
+
|---|---|
|
|
560
|
+
| `Core` | `Email`, `EmailValidator`, `TemplateManager`, `BounceManager` |
|
|
561
|
+
| `Delivery` | `UnifiedDeliveryQueue`, `MultiModeDeliverySystem`, `DeliveryStatus`, `UnifiedRateLimiter` |
|
|
562
|
+
|
|
563
|
+
## License and Legal Information
|
|
564
|
+
|
|
565
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
566
|
+
|
|
567
|
+
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
568
|
+
|
|
569
|
+
### Trademarks
|
|
570
|
+
|
|
571
|
+
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
|
572
|
+
|
|
573
|
+
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
|
574
|
+
|
|
575
|
+
### Company Information
|
|
576
|
+
|
|
577
|
+
Task Venture Capital GmbH
|
|
578
|
+
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
579
|
+
|
|
580
|
+
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
581
|
+
|
|
582
|
+
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
package/readme.plan.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Rust Migration Plan
|
|
2
|
+
|
|
3
|
+
## Completed Phases
|
|
4
|
+
|
|
5
|
+
### Phase 3: Rust Primary Backend (DKIM/SPF/DMARC/IP Reputation)
|
|
6
|
+
- Rust is the mandatory security backend — no TS fallbacks
|
|
7
|
+
- All DKIM signing/verification, SPF, DMARC, IP reputation through Rust bridge
|
|
8
|
+
|
|
9
|
+
### Phase 5: BounceManager + ContentScanner
|
|
10
|
+
- BounceManager bounce detection delegated to Rust `detectBounce` IPC command
|
|
11
|
+
- ContentScanner pattern matching delegated to new Rust `scanContent` IPC command
|
|
12
|
+
- New module: `rust/crates/mailer-security/src/content_scanner.rs` (10 Rust tests)
|
|
13
|
+
- ~215 lines removed from BounceManager, ~350 lines removed from ContentScanner
|
|
14
|
+
- Binary attachment scanning (PE headers, VBA macros) stays in TS
|
|
15
|
+
- Custom rules (runtime-configured) stay in TS
|
|
16
|
+
- Net change: ~-560 TS lines, +265 Rust lines
|
|
17
|
+
|
|
18
|
+
## Deferred
|
|
19
|
+
|
|
20
|
+
| Component | Rationale |
|
|
21
|
+
|-----------|-----------|
|
|
22
|
+
| EmailValidator | Already thin; uses smartmail; minimal gain |
|
|
23
|
+
| DNS record generation | Pure string building; zero benefit from Rust |
|
|
24
|
+
| MIME building (`toRFC822String`) | Sync in TS, async via IPC; too much blast radius |
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MAILER npm postinstall script
|
|
5
|
+
* Downloads the appropriate binary for the current platform from GitHub releases
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { platform, arch } from 'os';
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import https from 'https';
|
|
13
|
+
import { pipeline } from 'stream';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
import { createWriteStream } from 'fs';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
const streamPipeline = promisify(pipeline);
|
|
20
|
+
|
|
21
|
+
// Configuration
|
|
22
|
+
const REPO_BASE = 'https://code.foss.global/serve.zone/mailer';
|
|
23
|
+
const VERSION = process.env.npm_package_version || '1.0.0';
|
|
24
|
+
|
|
25
|
+
function getBinaryInfo() {
|
|
26
|
+
const plat = platform();
|
|
27
|
+
const architecture = arch();
|
|
28
|
+
|
|
29
|
+
const platformMap = {
|
|
30
|
+
'darwin': 'macos',
|
|
31
|
+
'linux': 'linux',
|
|
32
|
+
'win32': 'windows'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const archMap = {
|
|
36
|
+
'x64': 'x64',
|
|
37
|
+
'arm64': 'arm64'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const mappedPlatform = platformMap[plat];
|
|
41
|
+
const mappedArch = archMap[architecture];
|
|
42
|
+
|
|
43
|
+
if (!mappedPlatform || !mappedArch) {
|
|
44
|
+
return { supported: false, platform: plat, arch: architecture };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let binaryName = `mailer-${mappedPlatform}-${mappedArch}`;
|
|
48
|
+
if (plat === 'win32') {
|
|
49
|
+
binaryName += '.exe';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
supported: true,
|
|
54
|
+
platform: mappedPlatform,
|
|
55
|
+
arch: mappedArch,
|
|
56
|
+
binaryName,
|
|
57
|
+
originalPlatform: plat
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function downloadFile(url, destination) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
console.log(`Downloading from: ${url}`);
|
|
64
|
+
|
|
65
|
+
// Follow redirects
|
|
66
|
+
const download = (url, redirectCount = 0) => {
|
|
67
|
+
if (redirectCount > 5) {
|
|
68
|
+
reject(new Error('Too many redirects'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
https.get(url, (response) => {
|
|
73
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
74
|
+
console.log(`Following redirect to: ${response.headers.location}`);
|
|
75
|
+
download(response.headers.location, redirectCount + 1);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (response.statusCode !== 200) {
|
|
80
|
+
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
85
|
+
let downloadedSize = 0;
|
|
86
|
+
let lastProgress = 0;
|
|
87
|
+
|
|
88
|
+
response.on('data', (chunk) => {
|
|
89
|
+
downloadedSize += chunk.length;
|
|
90
|
+
const progress = Math.round((downloadedSize / totalSize) * 100);
|
|
91
|
+
|
|
92
|
+
// Only log every 10% to reduce noise
|
|
93
|
+
if (progress >= lastProgress + 10) {
|
|
94
|
+
console.log(`Download progress: ${progress}%`);
|
|
95
|
+
lastProgress = progress;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const file = createWriteStream(destination);
|
|
100
|
+
|
|
101
|
+
pipeline(response, file, (err) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
reject(err);
|
|
104
|
+
} else {
|
|
105
|
+
console.log('Download complete!');
|
|
106
|
+
resolve();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}).on('error', reject);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
download(url);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
console.log('===========================================');
|
|
118
|
+
console.log(' MAILER - Binary Installation');
|
|
119
|
+
console.log('===========================================');
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
const binaryInfo = getBinaryInfo();
|
|
123
|
+
|
|
124
|
+
if (!binaryInfo.supported) {
|
|
125
|
+
console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`);
|
|
126
|
+
console.error('');
|
|
127
|
+
console.error('Supported platforms:');
|
|
128
|
+
console.error(' • Linux (x64, arm64)');
|
|
129
|
+
console.error(' • macOS (x64, arm64)');
|
|
130
|
+
console.error(' • Windows (x64)');
|
|
131
|
+
console.error('');
|
|
132
|
+
console.error('If you believe your platform should be supported, please file an issue:');
|
|
133
|
+
console.error(' https://code.foss.global/serve.zone/mailer/issues');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`);
|
|
138
|
+
console.log(`Architecture: ${binaryInfo.arch}`);
|
|
139
|
+
console.log(`Binary: ${binaryInfo.binaryName}`);
|
|
140
|
+
console.log(`Version: ${VERSION}`);
|
|
141
|
+
console.log('');
|
|
142
|
+
|
|
143
|
+
// Create dist/binaries directory if it doesn't exist
|
|
144
|
+
const binariesDir = join(__dirname, '..', 'dist', 'binaries');
|
|
145
|
+
if (!existsSync(binariesDir)) {
|
|
146
|
+
console.log('Creating binaries directory...');
|
|
147
|
+
mkdirSync(binariesDir, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const binaryPath = join(binariesDir, binaryInfo.binaryName);
|
|
151
|
+
|
|
152
|
+
// Check if binary already exists and skip download
|
|
153
|
+
if (existsSync(binaryPath)) {
|
|
154
|
+
console.log('✓ Binary already exists, skipping download');
|
|
155
|
+
} else {
|
|
156
|
+
// Construct download URL
|
|
157
|
+
// Try release URL first, fall back to raw branch if needed
|
|
158
|
+
const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`;
|
|
159
|
+
const fallbackUrl = `${REPO_BASE}/raw/branch/main/dist/binaries/${binaryInfo.binaryName}`;
|
|
160
|
+
|
|
161
|
+
console.log('Downloading platform-specific binary...');
|
|
162
|
+
console.log('This may take a moment depending on your connection speed.');
|
|
163
|
+
console.log('');
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Try downloading from release
|
|
167
|
+
await downloadFile(releaseUrl, binaryPath);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.log(`Release download failed: ${err.message}`);
|
|
170
|
+
console.log('Trying fallback URL...');
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Try fallback URL
|
|
174
|
+
await downloadFile(fallbackUrl, binaryPath);
|
|
175
|
+
} catch (fallbackErr) {
|
|
176
|
+
console.error(`❌ Error: Failed to download binary`);
|
|
177
|
+
console.error(` Primary URL: ${releaseUrl}`);
|
|
178
|
+
console.error(` Fallback URL: ${fallbackUrl}`);
|
|
179
|
+
console.error('');
|
|
180
|
+
console.error('This might be because:');
|
|
181
|
+
console.error('1. The release has not been created yet');
|
|
182
|
+
console.error('2. Network connectivity issues');
|
|
183
|
+
console.error('3. The version specified does not exist');
|
|
184
|
+
console.error('');
|
|
185
|
+
console.error('You can try:');
|
|
186
|
+
console.error('1. Installing from source: https://code.foss.global/serve.zone/mailer');
|
|
187
|
+
console.error('2. Downloading the binary manually from the releases page');
|
|
188
|
+
|
|
189
|
+
// Clean up partial download
|
|
190
|
+
if (existsSync(binaryPath)) {
|
|
191
|
+
unlinkSync(binaryPath);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`✓ Binary downloaded successfully`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// On Unix-like systems, ensure the binary is executable
|
|
202
|
+
if (binaryInfo.originalPlatform !== 'win32') {
|
|
203
|
+
try {
|
|
204
|
+
console.log('Setting executable permissions...');
|
|
205
|
+
chmodSync(binaryPath, 0o755);
|
|
206
|
+
console.log('✓ Binary permissions updated');
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error(`⚠️ Warning: Could not set executable permissions: ${err.message}`);
|
|
209
|
+
console.error(' You may need to manually run:');
|
|
210
|
+
console.error(` chmod +x ${binaryPath}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log('✅ MAILER installation completed successfully!');
|
|
216
|
+
console.log('');
|
|
217
|
+
console.log('You can now use MAILER by running:');
|
|
218
|
+
console.log(' mailer --help');
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('For initial setup, run:');
|
|
221
|
+
console.log(' sudo mailer service enable');
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log('===========================================');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Run the installation
|
|
227
|
+
main().catch(err => {
|
|
228
|
+
console.error(`❌ Installation failed: ${err.message}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
});
|