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

@mathrunet/masamune_cloudflare_send_mail

v3.1.1

Published

Masamune framework package plugin for sending mail through Cloudflare Email Service (Email Sending) from Cloudflare Workers.

Downloads

315

Readme


[GitHub] | [YouTube] | [Packages] | [X] | [LinkedIn] | [mathru.net]

Masamune framework package plugin for sending mail through Cloudflare Email Service (Email Sending, beta) from Cloudflare Workers.

Two transports are supported.

| Transport | Use it when | How it sends | | --- | --- | --- | | binding | The sender domain is onboarded on the same Cloudflare account as the Worker. | Workers send_email binding (env.EMAIL.send()). | | api | The sender domain is onboarded on another Cloudflare account. | REST API POST https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/send with an API token. |

Also, masamune_functions_cloudflare can be used to execute server-side functions from methods defined on the client side, allowing for safe implementation.

Installation

Install the following packages

npm install @mathrunet/masamune_cloudflare_send_mail

Before sending, onboard the sender domain in the Cloudflare dashboard under Compute > Email Service > Email Sending > Onboard Domain. The domain must use Cloudflare DNS. See Send emails.

Setup

Binding transport

Add a send_email binding to your Wrangler configuration.

// wrangler.jsonc
{
  "send_email": [
    // "remote": true lets `wrangler dev` send real emails through the remote binding.
    { "name": "EMAIL", "remote": true }
  ]
}
# wrangler.toml
[[send_email]]
name = "EMAIL"
remote = true

The binding can be restricted with allowed_sender_addresses, allowed_destination_addresses or destination_address. See Configure send bindings.

If you use a binding name other than EMAIL, set it with the bindingName option or the MAIL_CLOUDFLARE_BINDING variable.

REST API transport

  1. On the account that owns the sender domain, create an API token with the Email Sending: Edit permission.
  2. Copy the account ID of that account.
  3. Register them in the Worker that sends mail.
npx wrangler secret put MAIL_CLOUDFLARE_API_TOKEN
// wrangler.jsonc
{
  "vars": {
    "MAIL_CLOUDFLARE_ACCOUNT_ID": "<account_id>"
  }
}

Never commit the API token. For local development, put it in .dev.vars.

Implementation

Library

import { sendMail, SendMailError } from "@mathrunet/masamune_cloudflare_send_mail";

interface Env {
  EMAIL: SendEmail; // From @cloudflare/workers-types. Any object with a compatible send() works.
  MAIL_CLOUDFLARE_ACCOUNT_ID: string;
  MAIL_CLOUDFLARE_API_TOKEN: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    try {
      const result = await sendMail({
        // Same-account domain:
        transport: { type: "binding", binding: env.EMAIL },
        // Another account's domain:
        // transport: { type: "api", accountId: env.MAIL_CLOUDFLARE_ACCOUNT_ID, apiToken: env.MAIL_CLOUDFLARE_API_TOKEN },
        message: {
          from: { email: "[email protected]", name: "Your Service" },
          to: ["[email protected]", { email: "[email protected]", name: "Jane Doe" }],
          replyTo: "[email protected]",
          subject: "Welcome!",
          text: "Thanks for signing up.",
          html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
        },
      });
      return Response.json(result);
    } catch (e) {
      if (e instanceof SendMailError) {
        // e.retryable is true for 429 / 5xx / network errors and retryable binding errors.
        return Response.json({ error: e.message, code: e.code }, { status: e.retryable ? 503 : 400 });
      }
      throw e;
    }
  },
};

Message

| Field | Type | Notes | | --- | --- | --- | | to | string \| SendMailAddress \| Array<string \| SendMailAddress> | Required. | | cc, bcc | same as to | Optional. to + cc + bcc must not exceed 50 addresses. | | from | string \| SendMailAddress | Required. Must belong to an onboarded domain. | | replyTo | string \| SendMailAddress | Optional. | | subject | string | Required. | | text, html | string | At least one is required. | | headers | Record<string, string> | Optional. Only allowed headers. |

SendMailAddress is { email: string; name?: string }. It is converted to { email, name } for the binding and to { address, name } for the REST API. replyTo is sent as replyTo to the binding and as reply_to to the REST API.

Result

| Field | binding | api | | --- | --- | --- | | messageId | messageId returned by send() | result.message_id | | delivered | always [] | result.delivered | | queued | always [] | result.queued | | permanentBounces | always [] | result.permanent_bounces | | suppressedRecipients | not set | result.suppressed_recipients when returned |

The binding does not report per-recipient status; a resolved promise means Cloudflare accepted the message.

Errors

sendMail throws SendMailError with status, code and retryable.

  • Invalid input: code is INVALID_MESSAGE or INVALID_TRANSPORT. Nothing is sent.
  • binding: code is the binding error code (e.g. E_SENDER_NOT_VERIFIED). E_RATE_LIMIT_EXCEEDED and E_INTERNAL_SERVER_ERROR are retryable.
  • api: status is the HTTP status and code is the first Cloudflare error code (e.g. 10001). 429 and 5xx are retryable, other 4xx are not. Network failures use NETWORK_ERROR and are retryable.

The API token is never included in error messages.

Functions

Import the package as follows and pass the list of functions you wish to define to the deploy function.

import * as m from "@mathrunet/masamune_cloudflare_send_mail";

export default m.deploy([
  // POST /send_mail
  m.Functions.sendMail(),
]);

The transport is resolved in the following order.

  1. The transport option.
  2. The send_email binding named bindingName / MAIL_CLOUDFLARE_BINDING / EMAIL in the Workers env (skipped when type: "api").
  3. The REST API with accountId / MAIL_CLOUDFLARE_ACCOUNT_ID and apiToken / MAIL_CLOUDFLARE_API_TOKEN (skipped when type: "binding").
m.Functions.sendMail({
  auth: new m.NoneAuthAdapter(), // Configure an appropriate authentication adapter in production.
  type: "api",
  accountId: "<account_id>",
});

Request body:

{
  "from": "[email protected]",
  "to": "[email protected]",
  "subject": "Welcome!",
  "text": "Thanks for signing up.",
  "html": "<h1>Welcome!</h1>"
}

Response body:

{
  "success": true,
  "messageId": "<[email protected]>",
  "delivered": ["[email protected]"],
  "queued": [],
  "permanentBounces": []
}

Invalid input returns 400, rate limiting returns 429, and other failures return 500 with { "error": "..." }.

Anyone who can call this endpoint can send mail from your domain. Always protect it with an authentication adapter and rules, and restrict senders with allowed_sender_addresses where possible.

GitHub Sponsors

Sponsors are always welcome. Thank you for your support!

https://github.com/sponsors/mathrunet