Skip to content

fix(miniflare): return EmailSendResult from send_email binding's send()#13577

Merged
dario-piotrowicz merged 3 commits intocloudflare:mainfrom
connyay:cjh/miniflare-send-email-result
Apr 20, 2026
Merged

fix(miniflare): return EmailSendResult from send_email binding's send()#13577
dario-piotrowicz merged 3 commits intocloudflare:mainfrom
connyay:cjh/miniflare-send-email-result

Conversation

@connyay
Copy link
Copy Markdown
Contributor

@connyay connyay commented Apr 17, 2026

The binding's send() resolved to undefined, diverging from production (and the public SendEmail type), which returns { messageId }. Workers that inspect the return value now see the same shape locally as deployed.

  • EmailMessage path: echo the parsed Message-ID with angle brackets stripped
  • MessageBuilder path: synthesize the id in the same <hex>@example.com form already used by the forward() path

  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: matching prod behavior

A picture of a cute animal (not mandatory, but encouraged)
https://www.youtube.com/watch?v=FykoipfMyNQ


Open with Devin

The binding's `send()` resolved to `undefined`, diverging from production
(and the public `SendEmail` type), which returns `{ messageId }`. Workers
that inspect the return value now see the same shape locally as deployed.

- EmailMessage path: echo the parsed Message-ID with angle brackets stripped
- MessageBuilder path: synthesize the id in the same `<hex>@example.com`
  form already used by the forward() path
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 17, 2026

🦋 Changeset detected

Latest commit: 2df9c5c

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Apr 17, 2026
@workers-devprod workers-devprod requested review from a team and dario-piotrowicz and removed request for a team April 17, 2026 03:08
@workers-devprod
Copy link
Copy Markdown
Contributor

workers-devprod commented Apr 17, 2026

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@connyay
Copy link
Copy Markdown
Contributor Author

connyay commented Apr 17, 2026

I hit this trying to wire up email sending in workers-rs 👉 cloudflare/workers-rs#975

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 17, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@13577

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@13577

miniflare

npm i https://pkg.pr.new/miniflare@13577

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@13577

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@13577

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@13577

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@13577

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@13577

wrangler

npm i https://pkg.pr.new/wrangler@13577

commit: 2df9c5c

Comment thread packages/miniflare/test/plugins/email/index.spec.ts Outdated
@connyay connyay requested a review from Skye-31 April 17, 2026 18:28
Copy link
Copy Markdown
Member

@dario-piotrowicz dario-piotrowicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks @connyay 🙏

Copy link
Copy Markdown
Contributor

@workers-devprod workers-devprod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk Apr 20, 2026
@dario-piotrowicz dario-piotrowicz merged commit e456952 into cloudflare:main Apr 20, 2026
63 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk Apr 20, 2026
@connyay connyay deleted the cjh/miniflare-send-email-result branch April 20, 2026 12:21
connyay added a commit to connyay/workers-rs that referenced this pull request Apr 20, 2026
guybedford added a commit to cloudflare/workers-rs that referenced this pull request Apr 29, 2026
* feat(worker): add send_email binding support

Adds a `SendEmail` binding and `EmailMessage` type so workers can dispatch
email via the Cloudflare Email Sending service configured under
`[[send_email]]` in wrangler.toml. Includes worker-sys bindings for
`cloudflare:email`, a runnable example under `examples/send-email`, and
integration tests.

* feat(worker): add structured Email builder for send_email binding

Extend the SendEmail binding to cover the public-beta builder overload in
addition to the raw MIME path. Adds `Email`/`EmailBuilder`, `EmailAddress`,
`EmailAttachment` (with `AttachmentContent::{Base64, Binary}`), and
`EmailSendResult { message_id }`. `SendEmail::send` now takes `&Email`;
the raw MIME path moves to `SendEmail::send_mime(&EmailMessage)`.

* address pr comments

* remove miniflare workaround

cloudflare/workers-sdk#13577 landed

* cleanup comments & example readme

* use ts-gen for email

* Auto-gen email bindings via ts-gen anonymous interface synthesis

Drops the hand-written Email/EmailBuilder/EmailAddress/EmailAttachment
types in worker/src/send_email.rs in favour of the auto-generated
SendEmailBuilder, EmailAddress, EmailAttachment, etc. that ts-gen now
synthesises from the d.ts. send_email.rs is reduced to the EnvBinding
trait impl on the auto-gen SendEmail extern type and re-exports.

types/email.d.ts renames the global EmailMessage interface to
StructuredEmailMessage to keep it unambiguously distinct from the
cloudflare:email-imported EmailMessage constructor class. The chompfile
prepends a `use email::EmailMessage` to the generated file so the
top-level send(message) signature resolves cross-module — removable
once ts-gen handles same-file module imports natively.

All 133 npm tests pass; both legacy raw-MIME and modern structured
send paths work end-to-end.

* add new_with_readable_stream test

* Pull cross-module qualification + new builder ergonomics from ts-gen

ts-gen learned three things since the last sync that simplify the
email surface here:

* Cross-module type references emit qualified Rust paths
  (`&email::EmailMessage` from a `Global` extern block referencing the
  `cloudflare:email` class). Drops the `chompfile.toml` postprocess
  that was prepending `use email::EmailMessage;` to the generated
  file.

* Built-in `web_sys` defaults — `Headers`, `Event`, `ReadableStream`,
  etc. resolve to `::web_sys::*` automatically, so those `--external`
  flags are redundant. Only the project-specific `Env` and
  `ExecutionContext` mappings remain in the chompfile.

* New dictionary builder shape: required fields go through the
  constructor, `build()` is infallible, literal discriminators
  collapse into the function name. Call sites update from
  `SendEmailBuilder::builder().from(x).build()?` to
  `SendEmailBuilder::builder(from, to, subject).build()` (or
  `::new(from, to, subject)` when no optionals are needed).

`types/email.d.ts` collapses to a single `class EmailMessage` inside
`declare module "cloudflare:email"`. The previous global-interface +
module-class split (mirroring upstream `@cloudflare/workers-types`)
was producing two distinct Rust types that both lowered to the same JS
object, which forced an `unchecked_ref` at the `reply()` call site.
Collapsed to one type they're indistinguishable in Rust.

`worker/src/send_email.rs` keeps the [`EnvBinding`] impl on top of
the auto-gen `SendEmail` extern type, plus a `#[cfg(test)]` compile
check that `SendEmail: Send` (which it is already, via the upstream
`JsValue: Send + Sync` change — no `unsafe impl Send` needed).

* Use FixedLengthStream Deref instead of unchecked_into in mime-stream test

`FixedLengthStream` already has `extends = web_sys::TransformStream` in
`worker-sys`, so wasm-bindgen auto-generates `Deref<Target = TransformStream>`
and `fixed.readable()` resolves through it. The previous
`fixed.unchecked_into::<web_sys::TransformStream>().readable()` was
unnecessarily defensive — drop the cast plus the now-unused `JsCast`
and `web_sys` imports.

* Fmt + advance ts-gen submodule to PR branch HEAD

CI's rustfmt --check flagged the dispatch_structured signature.
Apply fmt and bump the ts-gen submodule pointer to the latest
PR #8 commit (CONVENTIONS.md rationale + emit cleanup).

* Pull doc-comment generation from ts-gen PR #9

Each `new*` and `builder*` variant now ships with a doc block listing
its inlined literal discriminants under `# Inlined fields` and the
caller-supplied parameters under `# Parameters`, sourced from the
original getter JSDoc.

* Advance ts-gen submodule to merged main

ts-gen PR #9 (doc comments on dictionary builder variants) merged.
Bump the submodule pointer to the merge commit on main; the
generated `worker/src/email.rs` is unchanged from the PR-branch
output.

* Advance ts-gen submodule to merged main

ts-gen PR #10 (h2 headings + dash-separated bullets in builder docs)
merged. Bump the submodule pointer and regenerate
`worker/src/email.rs` with the updated doc format.

---------

Co-authored-by: Guy Bedford <gbedford@cloudflare.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

SendEmail.send returns a wrong type in development environment

5 participants