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

@hasna/mailery

v0.6.116

Published

Mailery email management CLI + MCP server + dashboard for Resend, AWS SES, and Cloudflare inbound workflows

Readme

@hasna/mailery

Mailery is an email management CLI + MCP server - send, receive, sync, and manage email via Resend, AWS SES, and Cloudflare-routed inbound mail.

npm License

Install

Mailery is built for the Bun runtime. Install Bun 1.3 or newer before installing the CLI with npm.

npm install -g @hasna/mailery

Open Core And Cloud

Users install the open-source package: @hasna/mailery.

Mailery stays local-first by default: local SQLite, local provider credentials, and local MCP. Self-hosted mode uses user-owned PostgreSQL/S3/SES as source of truth with local SQLite as a runtime cache. Mailery Cloud is an opt-in hosted source of truth at https://mailery.co; the same public CLI can sign up, create an agent API key, create a billing link, create hosted mailboxes, read hosted messages, generate hosted digests, and pull cloud mail into local SQLite. The per-domain readiness contract for local, self-hosted, and cloud operation lives in docs/DOMAIN_READINESS.md.

The SaaS control plane is private Hasna Tools infrastructure. End users and open-source contributors should not install or depend on private Hasna Tools platform packages.

@hasna/emails is compatibility-only during the rename period. New installs and docs should use @hasna/mailery; the compatibility name is expected to be retired after migration.

Quick Start

# Add a provider (SES or Resend)
mailery provider add --name production-ses --type ses --region us-east-1 --access-key ... --secret-key ...
mailery provider add --name production-resend --type resend --api-key ...

# Set up a domain (buy + DNS + SES in one command)
mailery domain setup example.com --provider <id> --email [email protected] ...

# Or connect a domain you already own without buying or calling Mailery Cloud
mailery domains connect example.com --provider <id> --source-of-truth local --dry-run
mailery domains connect example.com --provider <id> --source-of-truth postgres --dns-provider route53 --no-register-provider

# Or configure DNS for an existing domain via Cloudflare
mailery domain setup-cloudflare example.com --provider <id>

# Check public DNS before changing inbound routing
mailery domain check example.com

# SES send-only setup preserves existing MX, such as Google Workspace
mailery provision domain example.com --provider <ses-id> --dry-run

# Send an email
mailery send --from [email protected] --to [email protected] --subject "Hi" --body "Hello"

# Pull inbound mail from SES/S3 or Cloudflare-routed storage
mailery inbox source add-s3 --bucket <bucket> --prefix inbound/example.com/ --provider <provider-id>
mailery inbox sync-s3 --bucket <bucket> --prefix inbound/example.com/

# Inspect mailbox folders and ingestion sources
mailery inbox mailboxes
mailery inbox sources
mailery inbox list --folder unread --source provider:<id>

# Check sent email log
mailery email list

# Use self-hosted PostgreSQL/S3/SES as source of truth
mailery self-hosted status
mailery self-hosted migrate-local

Mailery Cloud

Cloud commands are non-interactive enough for agents and CI. Use --no-open when creating billing links from a headless environment.

# Show the hosted service status
mailery cloud --api-url https://mailery.co status

# Create or log into a hosted account, generate an agent API key, and create a
# hosted billing link without opening a browser
mailery cloud setup \
  --api-url https://mailery.co \
  --email [email protected] \
  --password "$MAILERY_PASSWORD" \
  --api-key-name "Agent CLI" \
  --scope mail_read mail_write billing_read \
  --billing \
  --no-open

# Hosted mailbox and message workflow
mailery cloud mailbox add [email protected] --provider manual
mailery cloud messages list --limit 20
mailery cloud messages pull --limit 20
mailery inbox list --limit 20

# Billing and domains
mailery cloud billing overview
mailery cloud billing subscribe --plan starter --no-open
mailery cloud domain available example-agent-mail.com
mailery cloud domain setup example-agent-mail.com --address agent --catch-all --mx-migration-consent

The starter SaaS plan is currently $10/month and grants hosted credits. Domain setup can return DNS records in safe planning mode before any domain purchase or MX migration is performed.

Domain Modes

Mailery is a multi-domain aggregator. Every domain is tracked independently, so DNS, inbound, outbound, and safety state belong to the domain, not to the app as a whole.

Use these setup paths:

| Mode | Who owns the mail source of truth | Domain setup path | | --- | --- | --- | | local | The local SQLite/files install | mailery domains add or mailery domains connect --source-of-truth local; DNS checks are advisory unless using a real send/receive provider. | | self_hosted | Your PostgreSQL/S3/SES or equivalent infrastructure | mailery domains connect --source-of-truth postgres, then publish the returned DNS tasks and enable inbound/outbound when evidence is ready. | | cloud | Mailery Cloud at https://mailery.co | mailery cloud domain setup; SaaS billing and tenant checks are handled by the hosted control plane. |

Authentication records are required only for the capability you enable:

  • Inbound aggregation needs an inbound route, usually MX plus SES/S3 or another configured source.
  • Outbound sending needs ownership verification plus DKIM and SPF/custom MAIL FROM alignment for the selected provider.
  • DMARC is per sending domain. It does not block local viewing or inbound aggregation, but it should be present before production sending and monitored before moving from p=none to stricter policies.

For self-hosted migration, run mailery self-hosted migrate-local once, switch to MAILERY_MODE=self_hosted with HASNA_EMAILS_STORAGE_MODE=remote, and treat PostgreSQL/S3 as the durable source of truth. Local SQLite is then only a runtime cache prepared from and flushed back to the self-hosted source.

Mailery UI (mailery ui)

A full-screen OpenTUI mail client with a responsive dashboard shell. Wide terminals use a two-column admin layout with persistent navigation, mailbox metrics, operations health, folders, actions, and a focused workspace. Inbox on wide terminals uses a split message list + preview reader. Narrow terminals collapse to a compact single-column view with the same Inbox, Compose, Domains, and Settings dialog. Inbox starts at all addresses and can be filtered to one email address when needed. Mailbox source status is exposed through CLI/API/MCP surfaces without treating provider credentials as inboxes. Live read-state, local refresh, background auto-pull, and an auto/light/dark color theme keep the mailbox current and readable across terminals.

mailery ui
mailery ui --mailbox unread

The app uses visible buttons and the Shortcuts command palette for actions. Mailbox filtering is handled by the mailbox dialog, which lists all mailboxes and configured/observed recipient addresses. Sidebar labels filter mailbox content, and Gmail-style Categories show Primary, Social, Promotions, Updates, and Forums separately from custom labels. Reader shows attachments with size/type. Composer writes markdown rendered to HTML on send. Settings opens as a simple menu dialog for sync, defaults, and display controls. Folders: Inbox · Unread · Starred · Sent · Archived · Spam · Trash.

Command Structure

mailery ui                # Mailbox UI - inbox, compose, domains, settings
mailery provider          # provider credentials/capabilities (ses, resend, sandbox)
mailery domain            # add/verify/buy/setup/dns/check domains
mailery address           # manage sender addresses (add, suspend, activate, quota)
mailery status            # redacted system status + next useful actions
mailery agent context     # agent-oriented context snapshot and workflows
mailery daemon            # background queue/realtime status and restart guidance
mailery logs tail         # local daemon/sync/inbound/scheduler log tails
mailery owner             # tenancy: register human/agent owners
mailery alias             # per-domain aliases + catch-all routing
mailery forwarding        # app-level forwarding for locally received/synced mail
mailery sendkey           # scoped send keys (restrict an agent to its own addresses)
mailery send              # send an email
mailery reply / forward   # reply (in-thread) or forward a sent/inbound email
mailery email             # sent email: list, search, show, replies, conversation
mailery inbox             # mailbox folders, sources, sync, read/star/archive/label, watch
mailery template          # email templates
mailery contact           # contacts (suppression list)
mailery group             # recipient groups
mailery sequence          # drip sequences
mailery schedule          # scheduled emails: list, cancel, run
mailery triage            # AI triage: classify, prioritize, draft replies
mailery storage           # self-hosted PostgreSQL storage: status, migrate, migrate-local, push, pull
mailery self-hosted       # source-of-truth runtime setup/status/migrate commands
mailery cloud             # optional Mailery Cloud signup/login/billing/mailbox/message/digest/domain workflow
mailery aws               # AWS setup: SES receipt rules, S3 inbound bucket
mailery config            # configuration (key=value)
mailery stats             # delivery statistics (--inbox for received mail)
mailery analytics         # email analytics
mailery doctor            # system diagnostics
mailery doctor delivery   # diagnose missing inbound mail for one address
mailery serve             # HTTP server + dashboard + authenticated /api/v1
mailery mcp               # install MCP server

Compact Output and Gradual Disclosure

Mailery CLI commands are compact by default so agent terminals do not fill with large records. List and status commands show essential fields, bounded row counts, and hints for the next detail command. Use these flags when you need more:

mailery address list              # compact table
mailery address list --verbose    # expanded owner/admin/quota rows
mailery domain status --verbose   # includes per-domain issue and fix lines
mailery provider list --limit 50  # explicit larger page
mailery contact list --suppressed # compact filtered contact list
mailery template show <name>      # detail path for template bodies
mailery sequence show <name>      # detail path for steps/enrollments
mailery forwarding list --source [email protected]
mailery agent context             # compact agent context summary
mailery agent context --verbose   # full redacted context snapshot
mailery agent context --json      # full machine-readable context
mailery config list --verbose     # full redacted config values
mailery config keys --verbose     # include examples for every key
mailery email show <id>           # detail path for one sent email
mailery inbox read <id>           # detail path for one inbound email

--json remains the machine-readable path. Broad MCP list tools default to their existing bounded summary page size for compatibility; use each tool's limit/offset inputs or the matching detail tool/resource for larger or full records. emails://agent/context is sampled for orientation; use emails://agent/context/full for the full redacted MCP resource.

Tenancy, aliases & scoped send keys

Every address can have an owner that is a human or an agent. A human-owned address must be administered by an agent (the agent operates it on the human's behalf); agent-owned addresses are self-administered.

# Register owners and assign an address (human-owned, agent-administered)
mailery owner register Morgan --type human --email [email protected]
mailery owner register Atlas  --type agent
mailery provision address [email protected] --provider <ses-id> --owner Morgan --administrator Atlas
mailery address owner [email protected]
mailery address set-owner [email protected] --owner Morgan --administrator Atlas
mailery address transfer-owner [email protected] --owner Atlas --reason "handoff" --yes
mailery address unassign-owner [email protected] --reason "retired" --yes
mailery address owner-history [email protected]

# Scoped send keys — an agent can only send from addresses it owns/administers
mailery sendkey create Atlas --label ci        # prints the esk_... token ONCE
mailery sendkey check  Atlas [email protected] # authorized
mailery sendkey list / revoke <id>

# Per-domain aliases + catch-all
mailery alias add [email protected] [email protected]
mailery alias catch-all example.com [email protected]   # *@example.com -> inbox@
mailery alias global [email protected]                  # protected global catch-all (ALL domains)
mailery alias resolve [email protected]              # show where it routes

# App-level forwarding: forwards only mail already received or synced locally.
# Use provider-native forwarding when the mailbox provider owns root MX.
mailery forwarding explain [email protected]
mailery forwarding add [email protected] [email protected] --provider <provider-id>
mailery forwarding run --provider <provider-id>            # future mail only
mailery forwarding run --provider <provider-id> --backfill # intentionally include older synced mail

# Address lifecycle
mailery address provision [email protected] --provider <ses-id> --owner Atlas
mailery address suggest --domain example.com
mailery address suspend <id>     # block sending from this address
mailery address activate <id>
mailery address quota <id> 200   # max 200 sends/day (use 'none' to clear)

DNS and inbound safety

mailery domain check <domain> detects common root MX owners, including Google Workspace, Microsoft 365, Cloudflare Email Routing, Zoho, Proton, and AWS SES. SES send-only provisioning does not require changing root MX and is the safest path when an existing mailbox provider already receives mail.

Publishing SES inbound MX is only for domains that should receive through SES/S3. Commands that can add SES inbound MX refuse to proceed when public MX already belongs to another provider. --force-mx-switch is available for intentional migrations after confirming mailbox ownership can move.

MCP Server

100+ tools for AI agents — send/read mail, provisioning, tenancy, aliases, scoped send keys, inbound read-state, real-time sync, agent context, source-aware mailbox status, ownership lookup/assignment/transfer audit, and verification-code waiting.

Terminology used by the CLI, REST API, MCP tools, and TUI:

  • Provider: credentials and capability, such as SES send rights, Resend API access, or a sandbox.
  • Source: an ingestion stream that brings mail into local storage, such as provider:<id>, s3:<bucket>, Cloudflare-routed inbound storage, legacy, or orphaned:<id>.
  • Mailbox: the user-visible scope being browsed, such as all mail, one address, or one domain.
  • Folder: a mailbox view such as inbox, unread, sent, starred, archived, spam, or trash.

Useful source-aware surfaces:

mailery inbox sources --json
mailery inbox mailboxes --source provider:<id> --json
mailery inbox search invoice --folder sent --source provider:<id> --json
curl 'localhost:3900/api/sources'
curl 'localhost:3900/api/mailboxes?source_id=legacy'
mailery-mcp

REST API

mailery serve exposes a dashboard plus two API surfaces:

  • Dashboard / management API under /api/* (providers, domains, addresses, emails, stats).
  • Authenticated programmatic API under /api/v1/* for agents/apps, keyed on a scoped send key (Authorization: Bearer esk_…). Every call is scoped to the key owner's addresses, so one caller can't act as another tenant:
mailery serve   # or: mailery-serve   (HOST=0.0.0.0 to allow other machines)

curl -H "Authorization: Bearer $ESK" localhost:3900/api/v1/addresses
curl -H "Authorization: Bearer $ESK" -X POST localhost:3900/api/v1/provision/address -d '{"email":"[email protected]"}'
curl -H "Authorization: Bearer $ESK" -X POST localhost:3900/api/v1/send -d '{"from":"[email protected]","to":"[email protected]","subject":"hi","text":"yo"}'
curl -H "Authorization: Bearer $ESK" 'localhost:3900/api/v1/inbox?limit=50&offset=0&search=invoice'  # scoped, paginated inbox

Library API

Import the stable local API from @hasna/mailery. The public entrypoint covers provider/domain/address CRUD, sending, inbound storage and listing, templates, contacts and suppression, sequences, exports, ownership helpers, and scoped send keys.

import {
  sendWithFailover,
  createProvider,
  createAddress,
  storeInboundEmail,
  createTemplate,
  suppressContact,
  createSequence,
  exportEmailsJson,
  createOwner,
  setAddressOwnerByRef,
  createSendKey,
} from "@hasna/mailery";

Inbound Email (AWS SES -> S3)

# Set up S3 bucket + SES receipt rules
mailery aws setup-inbound --domain example.com --bucket my-emails

# Pull received emails on demand
mailery inbox sync-s3 --bucket my-emails --prefix inbound/example.com/

# Read-state / organize (works for SES-S3, SMTP, Cloudflare-routed, and legacy imported mail)
mailery inbox list --unread            # filters: --unread/--read/--starred/--archived/--label <l>
mailery inbox latest [email protected] --json
mailery inbox wait [email protected] --timeout 120
mailery inbox wait-code [email protected] --from openai --timeout 120
mailery inbox sync-status --json       # S3 and realtime status
mailery inbox explain <id>             # route/owner/readiness trace
mailery inbox read <id>                # opening marks it read
mailery inbox star|archive|label <id>  # --undo / --remove to reverse

Real-time inbound (no manual sync)

Push delivery so mail lands automatically. setup-realtime wires SES → SNS → SQS (and attaches the topic to the receipt rule); watch long-polls and auto-syncs:

mailery inbox setup-realtime example.com   # creates SNS topic + SQS queue, saves the queue URL
mailery inbox watch                        # auto-delivers new mail in real-time (--once to poll once)

Alternatively, point an SNS HTTP subscription at POST /webhook/ses-inbound on mailery serve auto-confirms the subscription and syncs on each notification.

Self-Hosted Runtime (PostgreSQL/S3/SES)

The canonical local/self-hosted/cloud runtime contract lives in docs/SELF_HOSTED_RUNTIME.md. The per-domain aggregator and sending-readiness contract lives in docs/DOMAIN_READINESS.md. Together these documents are the source of truth for the active migration from local-authoritative mail to a self-hosted PostgreSQL/S3 runtime.

Mailery is local-first. The public OSS default is local SQLite and files under ~/.hasna/emails/, with no remote dependency. Self-hosted runtime is opt-in, and uses the emails slug for database URL compatibility: use HASNA_EMAILS_DATABASE_URL, not HASNA_MAILERY_DATABASE_URL.

For managed or self-hosted PostgreSQL, set HASNA_EMAILS_DATABASE_URL to the database connection string without printing or committing it. Self-hosted installs can use the fallback EMAILS_DATABASE_URL.

Mailery modes:

  • local - all reads/writes stay in local SQLite/files.
  • self_hosted - user/org-owned infrastructure. PostgreSQL is the source of truth for provider, mailbox, message, label, send, and state rows. S3 stores raw SES MIME and optional attachment objects. Local SQLite is a runtime cache that is prepared from PostgreSQL and flushed back after CLI commands; long running MCP/server processes flush periodically. For Hasna's own self-hosted deployment this means AWS RDS plus SES/S3, but the concrete cluster, bucket, and secret-path values live in private deployment secrets and are not package defaults.
  • cloud - Hasna-operated Mailery Cloud SaaS at https://mailery.co.

Deprecated remote and hybrid values are accepted as aliases only for the deployment mode (MAILERY_MODE, HASNA_EMAILS_MODE, or legacy config keys) and map to self_hosted. The lower-level storage sync mode remains separate: HASNA_EMAILS_STORAGE_MODE=remote means PostgreSQL source of truth with local runtime cache. HASNA_EMAILS_STORAGE_MODE=hybrid keeps local SQLite as source and only syncs when mailery storage pull, mailery storage push, or mailery storage sync --force is run explicitly.

# Configure RDS/PostgreSQL
export HASNA_EMAILS_DATABASE_URL="postgres://..."
# Optional self-hosted fallback:
# export EMAILS_DATABASE_URL="postgres://..."

# Optional explicit mode; default is local without a DB URL, self_hosted with one.
export MAILERY_MODE=self_hosted
export HASNA_EMAILS_STORAGE_MODE=remote

# Optional AWS/S3 settings for self-hosted inbound and attachments.
# Use your own bucket names and account-specific secrets.
export EMAILS_INBOUND_S3_BUCKET="your-mailery-inbound-bucket"
mailery config set attachment_storage s3
mailery config set attachment_s3_bucket "your-mailery-attachments-bucket"

# Check source-of-truth runtime status
mailery self-hosted status

# Apply PostgreSQL migrations
mailery self-hosted migrate

# One-time local SQLite → self-hosted PostgreSQL migration
mailery self-hosted migrate-local

Storage internals are intentionally kept off the default library entrypoint. Import them from the explicit subpath when building storage tooling:

import { getStorageStatus, prepareSelfHostedRuntimeCache } from "@hasna/mailery/storage";

See docs/SELF_HOSTED_RUNTIME.md for the source-of-truth contract.

Data

Stored in ~/.hasna/emails/ (SQLite + attachments).

Transport

The shared Streamable HTTP transport is the default (one process, many agents); pass --stdio for a per-client stdio server:

mailery-mcp                     # http://127.0.0.1:8861/mcp (default)
mailery-mcp --port 8861         # explicit port
mailery-mcp --stdio             # stdio transport (one server per client)
MCP_STDIO=1 mailery-mcp         # same
  • Health: GET http://127.0.0.1:8861/health -> {"status":"ok","name":"mailery"}
  • Override port with MCP_HTTP_PORT or --port

License

Apache-2.0 — see LICENSE