Skip to content

docs(email): warn that send_email is live under alchemy dev - #865

Open
zeyuri wants to merge 4 commits into
alchemy-run:mainfrom
zeyuri:docs/send-email-dev-warning
Open

docs(email): warn that send_email is live under alchemy dev#865
zeyuri wants to merge 4 commits into
alchemy-run:mainfrom
zeyuri:docs/send-email-dev-warning

Conversation

@zeyuri

@zeyuri zeyuri commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Under alchemy dev, a send_email binding is live — there is no local stub, so send() from a dev loop sends real mail from the configured domain. The remote-real design is documented on the local development page, but its examples are all storage (recoverable side effects), and the email guide never mentions dev behavior at the point where users wire up the binding.

This bit us in practice: we guarded our email transport with "if the binding's runtime object is undefined, log instead of sending", assuming dev leaves the binding absent. It doesn't — the object is a live proxy, and local test runs of an OTP flow made real send attempts.

  • cloudflare/email/send-and-receive.mdx: a :::caution on the binding section — live in dev, gate on an env var / ALCHEMY_STAGE instead of binding absence, and made-up recipient addresses are still real sends.
  • environments/local-development.mdx: one paragraph noting "everything is real" includes side-effectful bindings, not just storage.

@john-royal

Copy link
Copy Markdown
Contributor

The way Miniflare and Wrangler handle it, the message is console.logged unless you opt in to the real, remote binding. Is that something you'd want?

@zeyuri

zeyuri commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Yes — logging-by-default with an opt-in for real sends is what we'd want. The catch is that a faithful stub is entirely a cloudflare-runtime change, not an alchemy one, so I'd rather confirm placement with you before wiring it. Concretely:

In cloudflare-runtime (distilled):

  • New src/bindings/send-email/send-email.worker.ts — a stub makeBinding(env) returning { async send(message) { /* console.log from/to/subject */ } }, mirroring analytics-engine.worker.ts.
  • Register that stub as an extension module and add export const local = (props) => Plugin.useSync(SendEmail, () => ({ name: props.binding, wrapped: { moduleName, innerBindings } })) in SendEmail.ts, alongside remote. The cloudflare-internal:email EmailMessage shim (SendEmailLive) is reused as-is.

In alchemy:

  • An opt-in flag (remote?: boolean; Wrangler calls it experimental_remote) on the send_email descriptor built in Cloudflare/Email/SendBinding.ts, threaded from Cloudflare.Email.SendEmail(...) props.
  • LocalWorkerProvider.ts's case "send_email" switches on it: b.remote ? SendEmail.remote({...}) : SendEmail.local({...}).
  • These two docs pages then describe the new default (logged locally; remote: true for real sends).

Happy to implement in that order (runtime PR first, then submodule bump + alchemy wiring) if you're good with the placement and the opt-in name.

@john-royal

Copy link
Copy Markdown
Contributor

Yeah that sounds great! Go ahead - would be happy to review and merge when ready.

The one part of your plan that I'd alter is making the option flag dev?: { remote?: boolean } to make it clear that we're referring to making it remote in dev. Is that ok with you?

@zeyuri

zeyuri commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

dev: { remote: true } works well — consistent with the existing dev options on Worker. Implementing now: cloudflare-runtime stub PR first, then the alchemy wiring + these docs once it lands.

@zeyuri

zeyuri commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Runtime half is up: alchemy-run/cloudflare-tools#76 (the SendEmail.local stub, mirroring the analytics-engine one but logging from/to/subject). The alchemy wiring — dev: { remote: true } on the descriptor, LocalWorkerProvider switching on it, and rewritten versions of these two docs pages — is implemented and ready to PR the moment #76 lands and a cloudflare-runtime bump exists.

@john-royal john-royal self-assigned this Jul 21, 2026
@sam-goodwin

Copy link
Copy Markdown
Contributor

Is this ready to merge @john-royal ?

@john-royal

Copy link
Copy Markdown
Contributor

Docs need to be updated now that the local binding is implemented and remote now defaults to false. After that, good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants