// STRYKE-EMAIL — ENGINEERING REPORT

Opt-in stryke connector package · cdylib libstryke_email (publish=false) · namespace Email · v0.3.0 · 27 exports · JSON-in/JSON-out FFI over dlopen

>_EXECUTIVE SUMMARY

stryke-email is an opt-in connector package in the stryke ecosystem — transactional and campaign email through your own authenticated SMTP. It exposes single send, personalized mass mailing, {{merge}} templates, List-Unsubscribe headers, suppression lists, and rate limiting as the Email package.

Sending is built on lettre's blocking transport over rustls — no async runtime, no OpenSSL, no SaaS email API. The dependency set is small, durable, and vendorable, and ships once on demand rather than being linked into stryke core.

Email
stryke namespace
opt-in
Tier
cdylib
Load model (dlopen)
SMTP
Transport (lettre, rustls)
27
Public functions
v0.3.0
Version
no tokio
Runtime
MIT
License

~FFI MODEL

Each public symbol is a #[no_mangle] extern "C" fn email__* that takes a JSON string and returns a JSON string — one function per namespace method. The .stk wrapper (lib/Email.stk) builds the args dict, calls the symbol via the dlopened cdylib, and decodes the return. A return carrying an error key is converted into a die "Email::<fn>: <reason>"; otherwise the success payload is unwrapped to its meaningful field (value / ok / raw / groups / etc.). Connection params are merged into the request hash by a shared helper, with $SMTP_HOST / $SMTP_USER / $SMTP_PASS as the env fallback when host is absent. The 27 exports are pinned in stryke.toml's [ffi].exports and mirror the 27 fn Email:: wrappers one-to-one.


~TRANSPORT

Sending uses lettre's blocking SmtpTransport, which carries its own connection pool, over rustls TLS — no async runtime, no OpenSSL. One transport is cached per (host, port, tls, user) tuple, so a mass mailing reuses a single authenticated, pooled connection instead of reconnecting per message. The TLS mode (starttls / tls / none) selects the relay builder and the default port (587 / 465 / 25); an explicit port overrides the TLS-implied default. none uses builder_dangerous — intended for a local catch-all like MailHog, not for the open internet.


~THE CAMPAIGN LOOP

send_bulk iterates the recipient list once. Per recipient it validates the address, skips it if it is on the suppression set, merges the subject/text/html template with the recipient's vars (plus email and name), injects the List-Unsubscribe header (whose URL may itself be per-recipient via {{email}}), sends, and records { email, ok, error? }. The aggregate return is { sent, failed, skipped, results }. Throttling reads rate.delay_ms directly, or derives a millisecond delay from rate.per_minute; the sleep runs between sends, not after the last one. The per-recipient result set lets the caller retry failures and update its own bounce/opt-out state.


$TEMPLATING

The merge engine is a deliberate mustache-lite: {{ key }} substitution only, no conditionals or loops, unknown keys render empty, key names trimmed. String values insert verbatim; other JSON values insert their string form. The scan is byte-wise and UTF-8 safe. Keeping it logic-free avoids turning user-supplied template data into a template-injection surface. It is a pure function, unit-tested in the crate and exercised by the surface test with no SMTP server. render applies it to each of { subject, text, html }; merge_many applies one template across many var dicts.


%ADDRESS & VALIDATION

Address checks are conservative shape checks, not deliverability lookups (no MX/DNS): one @, a non-empty local part, a dotted domain with no leading/trailing dot and no whitespace. parse / split_addresses split "Name <user@host>" forms, honoring commas inside quotes and angle brackets. normalize drops the display name, trims, and lowercases only the domain (RFC 5321: the local part stays case-sensitive) — this is the canonical key used by dedupe and suppression matching. validate_message pre-flights a full message dict, returning { ok, errors } that flag missing/invalid from, no recipients across to/cc/bcc, all-invalid recipients, and an empty body.


&COMPLIANCE AS CODE

The package is scoped to legitimate sending — your own opted-in recipients, your own SMTP. List-Unsubscribe (RFC 2369 / 8058), suppression filtering, rate limiting, and address validation are first-class.

MechanismImplementation
List-UnsubscribeRFC 2369 / 8058 one-click header auto-added to every send_bulk message; URL templated per recipient
Suppressionsuppress_filter + send_bulk honor opt-outs and bounces before send
Rate limitingper_minute / delay_ms throttle for deliverability and provider limits
ValidationAddress checks before a message is queued

None of it removes the sender's legal obligation to obtain consent and honor unsubscribes (CAN-SPAM, GDPR); the package provides the mechanisms, not a way around them.


=FUNCTION SURFACE

27 public Email:: functions, grouped by role. Send functions take trailing SMTP %opts; the rest are network-free and CI-tested with no server. Full per-function signatures are in the documentation reference.

GroupFunctions
Sendsend, send_raw, send_bulk, send_template, build_message, verify_connection
Templatesrender, merge, merge_many
Addressesvalidate, validate_list, validate_message, parse, split_addresses, format_address, domain, normalize
Listsunsubscribe_header, suppress_filter, dedupe, chunk, group_by_domain, mailto
URLparse_url, build_url, redact_url
Metaversion

/DEPENDENCIES

lettre 0.11 with default-features = false (SMTP transport + message builder over rustls, hostname), serde / serde_json (the latter with preserve_order) for the FFI payloads, once_cell + parking_lot for the transport cache. A small, durable, vendorable set — no tokio, no OpenSSL, no SaaS email API client. The cdylib is publish = false: it ships through stryke's package channel, not crates.io.


#PROJECT METADATA

ItemValue
Version0.3.0
LicenseMIT
Crate typecdylib (libstryke_email, publish = false)
Public functions27 (Email namespace)
Repositorygithub.com/MenkeTechnologies/stryke-email
Parent languagestrykelang
Meta umbrellaMenkeTechnologiesMeta
Issuesgithub.com/MenkeTechnologies/stryke-email/issues