@mepmail/cli
Moves an email account to MepMail — the transactional email platform with a
Resend-compatible API and MCP built in:
contacts, segments, topics, properties, templates, webhooks, domains and
suppressions. Reads from the source provider, writes to your MepMail
instance (Cloud or self-hosted), and is safe to run again — a second run right
before cutover syncs what changed since the first.
Guide: https://docs.mepmail.dev/cli ·
Packages: https://docs.mepmail.dev/packages · Published as
@mepmail/cli on npm.
npx @mepmail/cli migrate --from resend
npx @mepmail/cli migrate plan --from resend --out plan.json
npx @mepmail/cli migrate apply plan.json --yes
npx @mepmail/cli migrate status
npx @mepmail/cli migrate rollback
Node 18 or newer; no dependencies.
What moves
| Contacts | Upserted by email through the batch endpoint; unsubscribed and topic opt-outs are preserved, never re-subscribed. Topic subscriptions and properties follow in two per-contact passes that run after everything else, so the account is sendable before they finish. |
| Segments, topics, properties | Matched by name / name / key: created when missing, updated when different, left alone when equal. Segment memberships follow the contacts. |
| Templates | Name, alias, subject, html, text. From, reply-to and variables cannot be stored — listed as manual steps. |
| Webhooks | Endpoint and events. Signing secrets are copied so receivers keep verifying (--fresh-webhook-secrets mints new ones, shown once). Events MepMail also emits carry over (email.*, contact.created, contact.updated, contact.deleted); the rest are dropped per webhook and listed. |
| Suppressions | Bounces, complaints and manual entries, with their origin. |
| Domains | Created with return path and tracking settings, in the SES region your MepMail instance serves (MepMail Cloud: us-east-1) — the Resend region does not carry over. DNS records must be added again (DKIM keys are per provider); the report prints a copy-ready table. Both providers can stay verified side by side. |
| Broadcasts | Drafts and scheduled ones import as drafts; sent ones are skipped unless --include-sent. |
| API keys | Not recreated (the source only exposes names); the report lists them as a to-do. |
Audiences (deprecated in Resend) are skipped — segments cover them.
Environment
RESEND_API_KEY | Source key (full access; the tool only ever reads). Alternatives: --from-key-stdin, or a masked prompt in a terminal. |
MEPMAIL_API_KEY | MepMail key (full access). Alternatives: --to-key-stdin, or a masked prompt. |
MEPMAIL_BASE_URL | API URL of a self-hosted MepMail instance. Same as --to-url. Unset, the target is MepMail Cloud (https://api.mepmail.dev); a terminal asks. |
NO_COLOR | Disables ANSI colors. |
DO_NOT_TRACK | Honored as a no-op: the tool sends no telemetry, never phones home and never checks for updates. |
Flags
mepmail --help lists every flag. The ones that change what happens:
--only a,b / --skip a,b — resource names: domains, properties, topics, segments, contacts, enrichment, broadcasts, templates, webhooks, suppressions, api-keys. enrichment is the per-contact pass (topic subscriptions, then properties) that runs last.
--rps N — requests per second against the source (default 8). Resend's team limit is 10, shared with your production sending; the CLI prints the limit it detects and warns above it. Go past 10 (up to 100) only after Resend raised your limit.
--on-conflict upsert|skip|error — contacts that already exist on the target (default upsert).
--include-sent, --fresh-webhook-secrets, --fresh (ignore the state file).
--yes, --non-interactive (automatic when stdin is not a terminal), --json (JSON on stdout, progress on stderr), --verbose, --color auto|always|never (--no-color = never).
Exit codes: 0 ok · 1 error · 2 plan has changes (plan only) · 3 partial, some items failed (details in the report).
Files
.mepmail/migrate-state.json (every id created, resume cursors, the plan
hash) and .mepmail/migrate-report.{json,md} are written next to where
you run the tool, mode 0600. .mepmail/ is appended to .gitignore when
one exists there. No file ever contains a key.
Security
Your Resend key never leaves your machine — the tool only ever contacts
api.resend.com and your MepMail API. Against Resend it sends GET requests
only, to documented endpoints. Keys live in memory for the duration of the
run: they are never written to a file and are redacted from every log line.
There is no telemetry, no update check, no third party.
Re-running and rolling back
Every run is a diff: existing rows are updated when they differ and left
alone when they match. Run mepmail migrate --from resend again right
before cutover to sync the contacts that arrived in between.
mepmail migrate rollback deletes only the ids the tool created (never
rows it merely updated), in reverse dependency order, after showing the list
and asking for confirmation.
Resend is a trademark of Plus Five Five, Inc. MepMail is not affiliated with or endorsed by Resend.