@frank/email (0.1.1)
Published 2026-09-26 16:28:28 +10:00 by steve
Installation
@frank:registry=npm install @frank/email@0.1.1"@frank/email": "0.1.1"About this package
@frank/email
Mautic marketing automation over its REST API, plus the inbound reply
verifier. Ported from Blockwise src/lib/mautic/* and src/lib/email/*
with behaviour unchanged. No network or database is touched at import.
Public API
identify(input)- upsert one Mautic contact now (search, then PATCH edit or POST new). Idempotent: a replayed transition returns the contact id.setStage(input, { enqueue })- queue a stage change (signed_up, trial_ending, trial_ended, paid, payment_failed, cancelled) as a durablemautic_syncjob with a sha256 dedupe key.fireEvent(input, { enqueue })- queue an event (campaign_live, budget_alert, new_leads) the same way.executeMauticSync(payload, { config, workspaceRow })- what the job worker runs; rechecks trial and billing guards before writing.parseEmailReplyIngest/recordEmailReply- the inbound reply contract; dedupe by sha256 key, consent suppression lives in therecord_email_replydatabase function.verifyEmailReplyRequest(request, body, secrets, verifier)- HMAC auth that refuses legacy bearer and any shared secret.configFromEnv(env?)- the only process.env reader; everything else takes config as a parameter. Formatters andresolveWorkspaceContactEmailare exported too; the trial reminder guarddecideTrialReminderSendcomes from@frank/billing.
Config
MAUTIC_API_URL, MAUTIC_API_USER, MAUTIC_API_PASSWORD; dedicated
BLOCKWISE_EMAIL_REPLY_AUTH_SECRET. Mautic field aliases use a fieldPrefix
parameter (default blockwise_).
Example
import { configFromEnv, identify, setStage } from "@frank/email";
const config = configFromEnv();
await setStage({ email, workspaceId, stage: "paid" }, { enqueue });
await identify({ email, fields: {}, config });
Dependencies
Dependencies
| ID | Version |
|---|---|
| @frank/billing | 0.1.0 |
Development dependencies
| ID | Version |
|---|---|
| @types/node | ^22.20.4 |