npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mailblastr-cli

v5.1.1

Published

Official command-line interface for the MailBlastr email API.

Readme

mailblastr-cli

Official command-line interface for the MailBlastr email API. Wraps the mailblastr Node.js SDK.

Install

npm i -g mailblastr-cli

Requires Node.js 18+.

Authentication

Set your API key once:

export MAILBLASTR_API_KEY=mb_xxxxxxxxx

Or pass --api-key mb_xxxxxxxxx to any command. Use MAILBLASTR_BASE_URL (or --base-url) to target a different API host.

Output

Every command prints the API response as pretty-printed JSON. Pass --json for raw compact JSON (handy for piping to jq). On failure a JSON error object is printed to stderr and the command exits 1; stdout stays JSON-only either way, so mailblastr … | jq never has to strip a diagnostic.

The error object is always { statusCode, name, message } — branch on name, never on message:

| Where it came from | statusCode | name | |---|---|---| | The API rejected the request | the HTTP status | the API's reason, e.g. validation_error, daily_quota_exceeded | | The request never reached the API | 0 | network_error | | The CLI rejected your flags before sending | null | cli_error |

Some API errors are a superset of that envelope and the extra fields are printed too: limit on plan/quota rejections, reputation on a reputation gate, and sent/sent_count on a partially applied emails batch.

One endpoint reports failure inside a 200 body rather than as an error: webhooks test returns { ok: false, error } when the delivery did not land. The CLI prints that body to stdout and still exits 1, so mailblastr webhooks test wh_123 && deploy behaves as you would expect.

--help works at every level: mailblastr --help, mailblastr emails --help, mailblastr emails send --help.

Pagination

List commands take --limit (integer 1100, default 20) plus one of --after / --before — cursors are item ids, and passing both is rejected. Responses are { "object": "list", "has_more": bool, "data": [...] }; page forward by feeding the last data[].id back as --after.

Two default page sizes exist. domains list, api-keys list, topics list, contacts list, contacts segments, contacts topics, segments list, segments contacts, campaigns list, contact-properties list, polls list, emails receiving list and emails receiving attachments skip the default 20 when you pass no pagination flag — but they do not return the whole collection: the response is capped at 1,000 rows, and has_more is true when that cap truncated it, so keep paging with --after. Everything else caps at 20 unless you raise --limit: emails list, templates list, webhooks list, audiences list, automations list, automations runs, events list and logs list.

A few endpoints are deliberately unpaginated and take no cursor flags — emails sources, emails attachments, emails receiving addresses and campaigns engagement (whose three lists are each capped at 500 rows server-side).

Usage

Emails

mailblastr emails send --from 'Acme <[email protected]>' --to '[email protected]' --subject 'hello' --html '<p>hi</p>'
mailblastr emails send --to '[email protected],[email protected]' \
  --template-id tmpl_welcome --variables '{"first_name":"Ada"}' --scheduled-at 2026-08-01T09:00:00Z
mailblastr emails list --limit 20
mailblastr emails list --status delivered --search 'invoice'
mailblastr emails list --folder scheduled      # mailbox folder: outbox | sent | scheduled | failed (anything else is a 422)
mailblastr emails sources                      # per-source metrics: one row per campaign/automation + api and individual one-off roll-ups
mailblastr emails get em_123
mailblastr emails update em_123 --scheduled-at 2026-08-02T09:00:00Z
mailblastr emails cancel em_123
mailblastr emails attachments em_123
mailblastr emails attachment em_123 att_456

--to, --cc, --bcc and --reply-to are repeatable and accept comma-separated values.

--from and --subject are required for an ordinary send but optional with --template-id / --template-alias, as the two template examples in this section show: omit them and the template's own stored from and subject are used. Supply them and your values win for good — the flags are sent verbatim, so editing and republishing the template stops changing the from and subject that command sends. The body still re-renders from the template on every send; only those two fields stop tracking it. An empty value is a choice rather than an omission: --subject '' ships a blank subject line, and --from '' is rejected with 422 missing_required_field.

[email protected] above is the mailbox simulator: it is intercepted before the provider is contacted and synthesizes the documented outcome (bounced@, complained@ and suppressed@ produce the other three; delivered@, bounced@ and complained@ also accept a +label suffix, suppressed@ does not). Two things to know when you script against it — it only fires on an immediate send, so pairing it with a future --scheduled-at skips the simulator and the address is treated as suppressed (422); and quota is debited per recipient exactly like a real send. Documentation domains (example.com, example.net, example.org, anything under .test / .invalid / .localhost / .example) are blocked outright and never reach a mailbox, so do not use them as stand-in recipients.

Attach files with --attachment <path> (read and base64-encoded locally) or --attachment-url <url> (fetched server-side). Both are repeatable; the API caps an attachment at 25 MB and a message at 40 MB decoded, and the CLI checks local files against those limits before sending.

mailblastr emails send --from [email protected] --to [email protected] --subject 'Your invoice' \
  --text 'Attached.' --attachment ./invoice.pdf
mailblastr emails send --to [email protected] \
  --template-alias welcome --variables '{"first_name":"Ada"}'

--idempotency-key accepts 1–255 characters, measured after the server trims the value — 255, not 256 — and is honoured only by emails send and emails batch. The CLI sends the key verbatim and lets the server be the authority: an out-of-range key comes back as 400 invalid_idempotency_key. Reusing a key with a different body is rejected (409 invalid_idempotent_request); replaying it with the same body returns the original response instead of sending twice. Every other command ignores the header, so a retry there creates a second resource.

Batch-send up to 100 emails in one request from a JSON file (an array of send payloads) or inline JSON:

mailblastr emails batch --file ./batch.json
mailblastr emails batch --data '[{"from":"[email protected]","to":["[email protected]"],"subject":"hi","text":"hello"}]'

Received (inbound) email

mailblastr emails receiving list --limit 20
mailblastr emails receiving addresses                                         # per-address inbound stats
mailblastr emails receiving get rem_123
mailblastr emails receiving attachments rem_123
mailblastr emails receiving attachment rem_123 att_456 --output invoice.pdf   # default filename: the attachment id
mailblastr emails receiving raw rem_123 --output message.eml                  # default filename: <id>.eml
mailblastr emails receiving forward rem_123 --from [email protected] --to [email protected]
mailblastr emails receiving reply rem_123 --from [email protected] --html '<p>thanks!</p>'
mailblastr emails receiving delete rem_123

attachment and raw download binary content: the file is written to --output (or the default filename) and the CLI prints where it was saved. reply requires at least one of --html / --text.

Domains

mailblastr domains add yourdomain.com
mailblastr domains list
mailblastr domains get dom_123
mailblastr domains verify dom_123
mailblastr domains update dom_123 --click-tracking --tls enforced
mailblastr domains update dom_123 --custom-return-path mail --receiving
mailblastr domains mx-check yourdomain.com
mailblastr domains records-csv dom_123 --output dns.csv   # default filename: <id>-dns-records.csv
mailblastr domains delete dom_123

records-csv answers text/csv, not JSON, so the CLI writes it to --output (or the default filename) and prints where it landed — the same shape as the emails receiving binary downloads.

One-click DNS — detect the provider, then apply the records via its API (auto-verifies after):

mailblastr domains dns detect dom_123
mailblastr domains dns cloudflare dom_123 --token cf_api_token
mailblastr domains dns godaddy dom_123 --key gd_key --secret gd_secret
mailblastr domains dns namecheap dom_123 --api-user ncuser --key nc_api_key

Claim a domain already verified by another account (start the claim, add the TXT record it returns, then verify):

mailblastr domains claim start yourdomain.com
mailblastr domains claim get dom_123
mailblastr domains claim verify dom_123

Contacts (domain-first)

Each sending domain has its own contact pool, so contacts create and contacts list need to be told which container to use — exactly one of --domain <domain> or --audience-id <id>; passing neither, or both, is a usage error:

mailblastr contacts create --domain yourdomain.com --email [email protected] --first-name Ada
mailblastr contacts list --domain yourdomain.com
mailblastr contacts list --domain yourdomain.com --segment-id seg_123
mailblastr contacts list --audience-id aud_123          # plain audiences instead of a domain pool
mailblastr contacts get [email protected] --domain yourdomain.com   # or by contact id, no --domain needed
mailblastr contacts update con_123 --unsubscribed
mailblastr contacts add-to-segment con_123 seg_123
mailblastr contacts remove-from-segment con_123 seg_123
mailblastr contacts topics con_123
mailblastr contacts set-topics con_123 --topics '[{"id":"top_123","subscription":"opt_out"}]'
mailblastr contacts delete con_123

Bulk-import a CSV. Files up to 5 MB / 10,000 rows go inline with --csv; anything larger is uploaded directly to storage first — mint a presigned URL, PUT the file to it, then finish the import with the storage_key it returned:

mailblastr contacts import aud_123 --csv ./contacts.csv --segment-id seg_123
mailblastr contacts import-upload aud_123 --csv ./big-list.csv     # → { storage_key, upload_url, max_bytes, ... }
curl -X PUT --upload-file ./big-list.csv "$UPLOAD_URL"
mailblastr contacts import aud_123 --storage-key "$STORAGE_KEY"

import takes exactly one of --csv / --storage-key. The upload_url is a short-lived bearer credential — don't log it or paste it into a shared shell history.

When the source is already structured, contacts batch imports a JSON array instead of a CSV (upsert by email, max 10,000 per call, exactly one of --file / --data):

mailblastr contacts batch aud_123 --file ./contacts.json               # audience as the positional…
mailblastr contacts batch --audience-id aud_123 --file ./contacts.json # …or as the flag, like every other contacts command
mailblastr contacts batch aud_123 --data '[{"email":"[email protected]","first_name":"Ada"}]' --on-conflict skip
# Domain-first: import straight into a domain's pool, no audience id needed.
mailblastr contacts batch --domain yourdomain.com --file ./contacts.json

Contact properties & audiences

mailblastr contact-properties create --key company --type string --fallback-value 'your company'
mailblastr contact-properties list
mailblastr contact-properties update prop_123 --fallback-value 'n/a'
mailblastr contact-properties update prop_123 --clear-fallback
mailblastr contact-properties delete prop_123

mailblastr audiences create --name Newsletter
mailblastr audiences list
mailblastr audiences update aud_123 --name 'Newsletter EU'
mailblastr audiences import-sheet aud_123 --url 'https://docs.google.com/spreadsheets/d/...' --segment-name 'July leads'
mailblastr audiences delete aud_123

Segments & topics

mailblastr segments create --domain yourdomain.com --name VIP
mailblastr segments create --domain yourdomain.com --name Actives --filter '{"status":"subscribed"}'
mailblastr segments list --domain yourdomain.com
mailblastr segments contacts seg_123

mailblastr topics create --domain yourdomain.com --name 'Product updates' --default-subscription opt_in
mailblastr topics list --domain yourdomain.com

Campaigns

mailblastr campaigns create --domain yourdomain.com --from 'Acme <[email protected]>' \
  --subject 'Summer sale' --html '<p>50% off</p>' --segment-id seg_123
mailblastr campaigns create --domain yourdomain.com --from 'Acme <[email protected]>' \
  --subject 'Weekly digest' --html '<p>...</p>' --recurrence weekly --unsubscribe-policy domain \
  --followups '[{"condition":"not_opened","delay":"2 days","html":"<p>Did you see this?</p>"}]'
mailblastr campaigns send camp_123
mailblastr campaigns send camp_123 --scheduled-at 2026-08-01T09:00:00Z
mailblastr campaigns send camp_123 --scheduled-at 'in 1 min'
mailblastr campaigns stats camp_123
mailblastr campaigns engagement camp_123    # who opened, clicked and replied (each list capped at 500 rows)
mailblastr campaigns ab camp_123
mailblastr campaigns cancel camp_123

Templates

mailblastr templates create --name Welcome --subject 'Hi {{first_name}}' --html '<p>Welcome!</p>'
mailblastr templates list
mailblastr templates duplicate tmpl_123 --name 'Welcome v2'
mailblastr templates publish tmpl_123

# Declare the variables the template uses, with their types and fallbacks
mailblastr templates update tmpl_123 \
  --variables '[{"key":"first_name","type":"string","fallback_value":"there"},{"key":"seats","type":"number","fallback_value":1}]'

--variables is the template's declared-variable registry, not the per-send values: a JSON array of {"key","type"?,"fallback_value"?} (max 50 entries; type is string or number, and a number fallback must parse as a number). A send prefers a declared fallback when the caller omits that variable, so a template with no registry has no fallbacks. Pass [] to clear it. Edits land on the draft — templates publish makes them live.

Automations & events

mailblastr automations create --name 'Welcome series' --domain yourdomain.com --trigger contact.created
mailblastr automations add-step auto_123 --type send_email --config '{"template_id":"tmpl_welcome"}'
mailblastr automations update-step auto_123 step_456 --type wait_for_event --config '{"event":"email.opened","timeout":"12 hours"}'
mailblastr automations delete-step auto_123 step_456
mailblastr automations update auto_123 --status enabled

# update-step replaces the step outright, so --type is required and --config is
# the complete new config. A step's graph key is immutable — delete and re-add
# with `add-step --key` to re-key it.
mailblastr automations runs auto_123
mailblastr automations runs auto_123 --status failed,skipped   # filtered before paging; repeatable
mailblastr automations run auto_123 run_456
mailblastr automations stop auto_123

# Author (or extend) the step graph from a prompt — the automation must be stopped, and this spends AI credits
mailblastr automations ai auto_123 --prompt 'Welcome new signups, then nudge anyone who has not opened after 2 days'
mailblastr automations ai auto_123 --prompt 'Send the upgrade nudge' --attach-from step_3 --attach-type condition_met

# Fire a custom event — only yourdomain.com's automations are triggered
mailblastr events send --domain yourdomain.com --name signup.completed --email [email protected] --data '{"plan":"pro"}'
mailblastr events list
mailblastr events create --name signup.completed --schema '{"plan":"string"}'
mailblastr events update evt_123 --schema '{"plan":"string","seats":"number"}'   # the name is immutable

Webhooks, API keys, logs, polls

mailblastr webhooks create --endpoint https://yourapp.com/hooks --events email.delivered,email.bounced
mailblastr webhooks rotate wh_123
mailblastr webhooks test wh_123
mailblastr webhooks verify --secret whsec_xxx --payload-file ./delivery.json \
  --svix-id msg_123 --svix-timestamp 1754000000 --svix-signature 'v1,base64sig'

mailblastr api-keys list

mailblastr logs list --limit 100 --method POST --status 429
mailblastr logs get log_123

mailblastr polls list
mailblastr polls get em_123

webhooks verify is the only command that makes no HTTP request: it recomputes the delivery signature locally and prints { valid, reason }. It is also the only command that needs no API key — it never contacts the API, so a webhook receiver can run it without holding a send-capable key. Pass the exact raw body bytes your endpoint received via --payload or --payload-file — re-serialized JSON will not match. --tolerance <seconds> caps timestamp skew (default 300; 0 skips the freshness check). Note the exit code is 0 whenever the check ran, whatever the verdict — branch on .valid, not on $? (unlike webhooks test, which exits 1 on a failed delivery).

API keys are read-only from the CLI

api-keys list is the whole group. There is no create, update or delete, and that is deliberate: keys are created, re-scoped and revoked in the MailBlastr dashboard, behind a signed-in session.

Every CLI invocation authenticates with an API key, so keeping key lifecycle out of the CLI means a key that leaks — from a shell history, a CI log, a .env someone pasted — cannot mint itself a replacement, widen its own permission or domain scope, or revoke the keys around it. Its blast radius stays fixed at what it could already do. The API enforces the same rule: POST /api-keys, PATCH /api-keys/:id and DELETE /api-keys/:id answer 403 dashboard_only to any API-key caller.

api-keys list still shows everything you need to audit: each key's non-secret prefix, permission, domain scoping and last_used_at.

Command names vs. SDK method names

The CLI's verbs are deliberately shorter than the corresponding SDK methods. A subcommand is already scoped by its resource path, so the CLI names every list after its plural noun and every download after the thing downloaded:

| CLI command | SDK method (mailblastr npm naming) | |---|---| | emails attachments <id> | emails.listAttachments | | emails receiving addresses | emails.receiving.listAddresses | | emails receiving attachments <id> | emails.receiving.listAttachments | | emails receiving raw <id> | emails.receiving.getRaw | | automations ai <id> | automations.createWithAi | | contacts import-upload <audienceId> | contacts.createImportUpload |

This is a shell-surface convention, not drift: the CLI applies the same shortening rule to every endpoint rather than to some.

The right-hand column is the Node.js SDK's spelling, because that is the library this CLI calls. Seven of the eight client libraries put the same method on the same nested resource and differ only in each language's casing and accessor syntax — listAttachments in npm, PHP ($mailblastr->emails->receiving) and Java (emails().receiving()), list_attachments in Python, Ruby and Rust, ListAttachments in Go (client.Emails.Receiving). The .NET SDK is the exception: it is a flat client, so read EmailListAttachmentsAsync, ReceivedEmailListAddressesAsync, ReceivedEmailListAttachmentsAsync, ReceivedEmailGetRawAsync, AutomationCreateWithAiAsync and ContactImportCreateUploadAsync instead of the dotted names above.

Documentation

Full API docs: https://www.mailblastr.com/docs

License

MIT