feat(worker): add send_email binding support#975
feat(worker): add send_email binding support#975guybedford merged 15 commits intocloudflare:mainfrom
Conversation
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.
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)`.
|
Duplicate The field comment on
So duplicates silently collapse to the last value instead of being preserved. Two ways to tighten:
(1) is simpler and fine given the runtime can't represent duplicates anyway. |
|
The runtime treats attachment string content as UTF-8 bytes, not base64. The function in edgeworker that feeds the mail stream does this: kj::ArrayPtr<kj::byte> getArrayPtrFromContent(kj::OneOf<kj::String, jsg::BufferSource>& content) {
KJ_SWITCH_ONEOF(content) {
KJ_CASE_ONEOF(string, kj::String) { return string.asBytes(); }
KJ_CASE_ONEOF(buffer, jsg::BufferSource) { return buffer.asArrayPtr(); }
}
}
(Sidebar: the public docs at Suggested fix — rename to reflect actual semanticspub enum AttachmentContent {
Text(String), // sent as UTF-8 bytes on the wire
Bytes(Vec<u8>), // sent as Uint8Array
}with the obvious |
|
@connyay I'd be interested to get your feedback here - I was able to update this API to use a new automated bindgen from ts-gen as an additional commit, which basically just automates the |
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.
Merging this PR will not alter performance
|
|
@guybedford that looks great! ts-gen should be a slam dunk for flagship #979 👀 - I can rework that after this lands or you are welcome to use that as a testbed as it should be a pretty straight forward generation. few notes on email:
A couple of options: have ts-gen emit 2. |
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).
…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.
|
@connyay I've fixed up the
For (3), builders are important for providing optimized bindgen (this is a future optimization we want to add, not just relying on serde for our own sledgehammer style bindings). I've added a few fixes for the ergonomics here, please take another look:
|
CI's rustfmt --check flagged the dispatch_structured signature. Apply fmt and bump the ts-gen submodule pointer to the latest PR cloudflare#8 commit (CONVENTIONS.md rationale + emit cleanup).
|
This is delightful - ship it! |
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.
ts-gen PR cloudflare#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.
ts-gen PR cloudflare#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.
Adds a
SendEmailbinding andEmailMessagetype so workers can dispatch email via the Cloudflare Email Sending service configured under[[send_email]]in wrangler.toml. Includes worker-sys bindings forcloudflare:email, a runnable example underexamples/send-email, and integration tests.