Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "wasm-streams"]
path = wasm-streams
url = git@github.com:guybedford/wasm-streams.git
[submodule "ts-gen"]
path = ts-gen
url = git@github.com:wasm-bindgen/ts-gen
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions chompfile.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
version = 0.1

[[task]]
name = 'build:types'
deps = ['install:ts-gen']
# `Env` / `ExecutionContext` are project-specific re-exports that ts-gen
# can't infer; everything else (`ReadableStream`, `Headers`, `Event`, …)
# resolves through ts-gen's built-in web_sys defaults.
run = '''ts-gen --input types/email.d.ts --output worker/src/email.rs \
--external "Env=crate::Env" \
--external "ExecutionContext=crate::Context"'''

[[task]]
name = 'install:ts-gen'
# ts-gen pulls in oxc which needs a newer rustc than the workspace's pinned 1.88;
# build with the user's stable toolchain instead so it ignores rust-toolchain.toml.
run = 'cargo +stable install --path ts-gen'

[[task]]
name = 'build:wasm-bindgen'
cwd = 'wasm-bindgen'
Expand Down
18 changes: 18 additions & 0 deletions examples/send-email/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "send-email-on-workers"
version = "0.1.0"
edition = "2021"

[package.metadata.release]
release = false

[lib]
crate-type = ["cdylib"]

[dependencies]
# Default feature `gethostname` pulls in a crate that doesn't build for
# `wasm32-unknown-unknown`, so disable it. The remaining core API is enough
# to assemble a message as long as we set `date` and `message_id` ourselves
# (the auto-generated ones rely on `SystemTime::now()` / `gethostname`).
mail-builder = { version = "0.4", default-features = false }
worker.workspace = true
28 changes: 28 additions & 0 deletions examples/send-email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Sending email from Cloudflare Workers

Example of using `worker::SendEmail` to send a message through a `[[send_email]]` binding.

Two routes:

* `GET /` — the structured path. Set fields like `from`, `to`, `subject`, and `text`/`html` on [`Message::builder`](https://docs.rs/worker/latest/worker/struct.MessageBuilder.html), and the runtime assembles the MIME body for you.
* `GET /raw` — the raw MIME path. Build the body yourself with [`mail-builder`](https://crates.io/crates/mail-builder) and hand it to [`EmailMessage`](https://docs.rs/worker/latest/worker/struct.EmailMessage.html) as-is. Reach for this when you need control over the MIME — custom headers, DKIM passthrough, VERP bounces, that sort of thing.

## Local development

`wrangler dev --local` won't actually send anything. As the [Cloudflare docs](https://developers.cloudflare.com/email-routing/email-workers/local-development/) explain, outbound messages get written to a local `.eml` file. Wrangler prints the path so you can open it and check the raw message.

```bash
npm install
npm run dev
# then, in another shell:
curl http://localhost:8787/ # structured
curl http://localhost:8787/raw # raw MIME
```

## Deploying

Verify the sender and recipient addresses first (see the [Cloudflare email API docs](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/)), then:

```bash
npm run deploy
```
12 changes: 12 additions & 0 deletions examples/send-email/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "send-email-on-workers",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "cargo install worker-build ; wrangler deploy",
"dev": "cargo install worker-build ; wrangler dev --local"
},
"devDependencies": {
"wrangler": "^4.83.0"
}
}
55 changes: 55 additions & 0 deletions examples/send-email/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use mail_builder::MessageBuilder as MimeBuilder;
use worker::*;

const SENDER: &str = "sender@example.com";
const RECIPIENT: &str = "recipient@example.com";

#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
let sender = env.send_email("EMAIL")?;

let result = match req.path().as_str() {
"/" => send_structured(&sender).await?,
"/raw" => send_raw_mime(&sender).await?,
// Don't dispatch on favicon / unknown paths — otherwise every browser
// tab to localhost sends a real email in `wrangler dev`.
_ => return Response::error("not found", 404),
};

Response::ok(format!("sent: {}", result.message_id()))
}

async fn send_structured(sender: &SendEmail) -> Result<EmailSendResult> {
let from = EmailAddress::new("Sending email test", SENDER);
let builder = SendEmailBuilder::builder_with_email_address_and_str(
&from,
RECIPIENT,
"An email generated in a Worker",
)
.text("Congratulations, you just sent an email from a Worker.")
.html("<p>Congratulations, you just sent an email from a Worker.</p>")
.build();

Ok(sender.send_with_builder(&builder).await?)
}

async fn send_raw_mime(sender: &SendEmail) -> Result<EmailSendResult> {
// mail-builder's auto-generated `Date:` and `Message-ID:` headers rely on
// `SystemTime::now()` and `gethostname`, neither of which work on
// `wasm32-unknown-unknown`. https://github.com/stalwartlabs/mail-builder/pull/26
let now_ms = Date::now().as_millis();
let message_id = format!("{now_ms}@example.com");

let raw = MimeBuilder::new()
.from(("Sending email test", SENDER))
.to(RECIPIENT)
.subject("An email generated in a Worker")
.date((now_ms / 1000) as i64)
.message_id(message_id)
.text_body("Congratulations, you just sent an email from a Worker.")
.write_to_string()
.map_err(|e| Error::RustError(e.to_string()))?;

let message = EmailMessage::new(SENDER, RECIPIENT, &raw)?;
Ok(sender.send(&message).await?)
}
11 changes: 11 additions & 0 deletions examples/send-email/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name = "send-email-on-workers"
main = "build/index.js"
compatibility_date = "2024-10-01"

[build]
command = "cargo install \"worker-build@^0.8\" && worker-build --release"
# For development: use local worker-build binary
# command = "../../target/release/worker-build --release"

[[send_email]]
name = "EMAIL"
Loading
Loading