Skip to content

Commit d639b16

Browse files
committed
feat: add email worker support
1 parent 6a5bfc1 commit d639b16

File tree

11 files changed

+381
-0
lines changed

11 files changed

+381
-0
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/email/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "email"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[package.metadata.release]
7+
release = false
8+
9+
# https://github.com/rustwasm/wasm-pack/issues/1247
10+
[package.metadata.wasm-pack.profile.release]
11+
wasm-opt = false
12+
13+
[lib]
14+
crate-type = ["cdylib"]
15+
16+
[dependencies]
17+
worker = { workspace=true }
18+
console_error_panic_hook = { version = "0.1.1" }

examples/email/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use core::str;
2+
3+
use worker::*;
4+
5+
#[event(email)]
6+
async fn email(message: ForwardableEmailMessage, _env: Env, _ctx: Context) -> Result<()> {
7+
console_error_panic_hook::set_once();
8+
9+
let allow_list = ["[email protected]", "[email protected]"];
10+
let from = message.from_envelope();
11+
12+
let raw: Vec<u8> = message.raw_bytes().await?;
13+
let raw = str::from_utf8(&raw)?;
14+
console_log!("Raw email: {}", raw);
15+
16+
if allow_list.contains(&from.as_str()) {
17+
message.forward("[email protected]", None).await?;
18+
} else {
19+
message.set_reject("Address not allowed")?;
20+
}
21+
Ok(())
22+
}

examples/email/wrangler.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "email-worker"
2+
main = "build/worker/shim.mjs"
3+
compatibility_date = "2024-08-13"
4+
5+
[build]
6+
command = "cargo install -q worker-build && worker-build --release"

worker-build/src/js/shim.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class Entrypoint extends WorkerEntrypoint {
2020
async scheduled(event) {
2121
return await imports.scheduled(event, this.env, this.ctx)
2222
}
23+
24+
async email(message) {
25+
return await imports.email(message, this.env, this.ctx)
26+
}
2327
}
2428

2529
const EXCLUDE_EXPORT = [
@@ -33,6 +37,7 @@ const EXCLUDE_EXPORT = [
3337
"fetch",
3438
"queue",
3539
"scheduled",
40+
"email",
3641
"getMemory"
3742
];
3843

worker-macros/src/event.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
1212
Start,
1313
#[cfg(feature = "queue")]
1414
Queue,
15+
Email,
1516
}
1617
use HandlerType::*;
1718

@@ -28,6 +29,7 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
2829
"respond_with_errors" => {
2930
respond_with_errors = true;
3031
}
32+
"email" => handler_type = Some(Email),
3133
_ => panic!("Invalid attribute: {}", attr),
3234
}
3335
}
@@ -183,6 +185,45 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
183185

184186
TokenStream::from(output)
185187
}
188+
Email => {
189+
let input_fn_ident = Ident::new(
190+
&(input_fn.sig.ident.to_string() + "_email_glue"),
191+
input_fn.sig.ident.span(),
192+
);
193+
let wrapper_fn_ident = Ident::new("email", input_fn.sig.ident.span());
194+
// rename the original attributed fn
195+
input_fn.sig.ident = input_fn_ident.clone();
196+
197+
let wrapper_fn = quote! {
198+
pub async fn #wrapper_fn_ident(message: ::worker::worker_sys::ForwardableEmailMessage, env: ::worker::Env, ctx: ::worker::worker_sys::Context) {
199+
// call the original fn
200+
let ctx = worker::Context::new(ctx);
201+
match #input_fn_ident(::worker::ForwardableEmailMessage::from(message), env, ctx).await {
202+
Ok(()) => {},
203+
Err(e) => {
204+
::worker::console_log!("{}", &e);
205+
panic!("{}", e);
206+
}
207+
}
208+
}
209+
};
210+
211+
let wasm_bindgen_code =
212+
wasm_bindgen_macro_support::expand(TokenStream::new().into(), wrapper_fn)
213+
.expect("wasm_bindgen macro failed to expand");
214+
215+
let output = quote! {
216+
#input_fn
217+
218+
mod _worker_email {
219+
use ::worker::{wasm_bindgen, wasm_bindgen_futures};
220+
use super::#input_fn_ident;
221+
#wasm_bindgen_code
222+
}
223+
};
224+
225+
TokenStream::from(output)
226+
}
186227
Start => {
187228
// save original fn name for re-use in the wrapper fn
188229
let input_fn_ident = Ident::new(

worker-sys/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod crypto;
44
mod d1;
55
mod durable_object;
66
mod dynamic_dispatcher;
7+
mod email;
78
mod fetcher;
89
mod fixed_length_stream;
910
mod hyperdrive;
@@ -23,6 +24,7 @@ pub use crypto::*;
2324
pub use d1::*;
2425
pub use durable_object::*;
2526
pub use dynamic_dispatcher::*;
27+
pub use email::*;
2628
pub use fetcher::*;
2729
pub use fixed_length_stream::*;
2830
pub use hyperdrive::*;

worker-sys/src/types/email.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use js_sys::Promise;
2+
use wasm_bindgen::prelude::*;
3+
use web_sys::{Headers, ReadableStream};
4+
5+
#[wasm_bindgen]
6+
extern "C" {
7+
#[wasm_bindgen(extends=js_sys::Object)]
8+
#[derive(Clone, PartialEq, Eq)]
9+
pub type EmailMessage;
10+
11+
// TODO(lduarte): see if also accepting string is needed
12+
#[wasm_bindgen(constructor, catch)]
13+
pub fn new(from: &str, to: &str, raw: &str) -> Result<EmailMessage, JsValue>;
14+
15+
#[wasm_bindgen(method, getter)]
16+
pub fn from(this: &EmailMessage) -> String;
17+
18+
#[wasm_bindgen(method, getter)]
19+
pub fn to(this: &EmailMessage) -> String;
20+
}
21+
22+
#[wasm_bindgen]
23+
extern "C" {
24+
#[wasm_bindgen(extends=js_sys::Object)]
25+
#[derive(Clone, PartialEq, Eq)]
26+
pub type ForwardableEmailMessage;
27+
28+
#[wasm_bindgen(method, getter)]
29+
pub fn from(this: &ForwardableEmailMessage) -> String;
30+
31+
#[wasm_bindgen(method, getter)]
32+
pub fn to(this: &ForwardableEmailMessage) -> String;
33+
34+
#[wasm_bindgen(method, getter)]
35+
pub fn raw(this: &ForwardableEmailMessage) -> ReadableStream;
36+
37+
// File size will never pass over 4GB so u32 is enough
38+
#[wasm_bindgen(method, getter, js_name=rawSize)]
39+
pub fn raw_size(this: &ForwardableEmailMessage) -> u32;
40+
41+
#[wasm_bindgen(method, catch, js_name=setReject)]
42+
pub fn set_reject(this: &ForwardableEmailMessage, reason: &str) -> Result<(), JsValue>;
43+
44+
#[wasm_bindgen(method, catch)]
45+
pub fn forward(
46+
this: &ForwardableEmailMessage,
47+
rcpt_to: &str,
48+
headers: Headers,
49+
) -> Result<Promise, JsValue>;
50+
51+
#[wasm_bindgen(method, catch)]
52+
pub fn reply(this: &ForwardableEmailMessage, email: EmailMessage) -> Result<Promise, JsValue>;
53+
}
54+
55+
#[wasm_bindgen]
56+
extern "C" {
57+
#[wasm_bindgen(extends=js_sys::Object)]
58+
#[derive(Clone, PartialEq, Eq)]
59+
pub type SendEmail;
60+
61+
#[wasm_bindgen(method, catch)]
62+
pub fn send(this: &SendEmail, email: EmailMessage) -> Result<Promise, JsValue>;
63+
64+
}

0 commit comments

Comments
 (0)