fix(miniflare): return EmailSendResult from send_email binding's send()#13577
Merged
dario-piotrowicz merged 3 commits intocloudflare:mainfrom Apr 20, 2026
Merged
Conversation
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 detectedLatest 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 |
Contributor
|
Codeowners approval required for this PR:
Show detailed file reviewers |
Contributor
Author
|
I hit this trying to wire up email sending in workers-rs 👉 cloudflare/workers-rs#975 |
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
create-cloudflare
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-editor-shared
wrangler
commit: |
edevil
approved these changes
Apr 17, 2026
Skye-31
reviewed
Apr 17, 2026
dario-piotrowicz
approved these changes
Apr 20, 2026
Member
dario-piotrowicz
left a comment
There was a problem hiding this comment.
Looks good to me, thanks @connyay 🙏
workers-devprod
approved these changes
Apr 20, 2026
Contributor
workers-devprod
left a comment
There was a problem hiding this comment.
Codeowners reviews satisfied
Merged
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The binding's
send()resolved toundefined, diverging from production (and the publicSendEmailtype), which returns{ messageId }. Workers that inspect the return value now see the same shape locally as deployed.<hex>@example.comform already used by the forward() pathA picture of a cute animal (not mandatory, but encouraged)
https://www.youtube.com/watch?v=FykoipfMyNQ