feat(EmailWorkers): Implement email worker functionality#715
feat(EmailWorkers): Implement email worker functionality#715nudded wants to merge 1 commit intocloudflare:mainfrom
Conversation
|
Does this part need to be a specific format? Or can it be any string? let msg = format!(
"From: Cloudflare bot <{}>
To: Toon <{}>
In-Reply-To: {}
Message-ID: <{}@redacted.com>
Subject: Email well received!
I've parsed the mail!
"What I am wondering is if it needs a specific format, maybe it should use the type system to enforce it? If it's just any old string, then disregard |
|
What about an API like this: use uuid::Uuid;
use worker::*;
#[event(email)]
async fn main(message: EmailMessage, _env: Env, _ctx: Context) -> Result<()> {
let new_message = EmailMessage::try_from(
RawEmailMessage::builder()
.from_name("From Name")
.from_email(&message.to_email())
.to_name("To Name")
.to_email(&message.from_email())
.subject("Email well received!")
.date("Sat, 15 Mar 2025 22:06:02 +0000")
.message_id(format!("{}@redacted.com", Uuid::new_v4()))
.in_reply_to(message.headers().get("Message-ID")?.unwrap())
.message("I've parsed the mail!")
.build(),
);
message.reply(new_message).await?;
Ok(())
}With the type system enforcing mandatory fields at compile time and removing any risk of user formatting errors? |
|
Actually, on second thought, maybe |
yeah, In my testing not a lot of the existing crates seem to work well, so it might be useful to create a small crate that fills this gap (but should imho not be part of this crate) |
|
This looked promising: https://docs.rs/mail-builder/latest/mail_builder/index.html |
Tried this, got a CPU limit exceeded error on Cloudflare. (I think it's related to it adding the |
|
@zebp If you have some time for a review :) |
|
I tried out these changes, and I was able to receive and reply to emails just fine. I also used the mail_parser and mail_builder crates, and I found that they worked pretty well in combination with workers. The one thing I noticed is that you have to explicitly set a date and message id when building a message because otherwise the defaults will panic when it tries to get the date or generate a random id. It would be nice if functionality for the SendEmail binding could also be added. I tried adding it myself I was also able to send emails that way. I just reused the binding for an EmailMessage and it worked to call send(). |
@jeholliday would you be able to share what you wrote to implement the |
@devnull03 Sure, sorry it took a few days. I have committed the changes needed to get the SendEmail binding working in jeholliday@f05b750 This is an excerpt from where I am successfully using it: use mail_builder::MessageBuilder;
use uuid::Uuid;
use worker::*;
async fn send_email(subject: &str, body: &str, env: &Env) -> Result<()> {
let from = "<from_email>";
let to = "<to_email>";
let msg = MessageBuilder::new()
.from(from)
.to(to)
.subject(subject)
.date(Date::now().as_millis() / 1000)
.message_id(format!("{}@<my_domain>", Uuid::new_v4()))
.text_body(body)
.write_to_string()
.unwrap();
let msg = EmailMessage::new(from, to, &msg)?;
let seb = env.send_email("SEB")?;
seb.send(msg).await?;
Ok(())
}This uses a binding named SEB from my send_email = [
{ name = "SEB", destination_address = "<to_email>" },
]Edit: BTW I remember it was very annoying to get the Also if you are trying to use this branch, make sure you use the |
|
I was able to get this to work with MessageBuilder for replying to incoming e-mails but one thing to note is that Instead, you need to set the header manually using a raw header value. let msg_id = msg.headers().get("Message-ID")?.unwrap();
let reply_msg = MessageBuilder::new()
.header("In-Reply-To", HeaderType::from(Raw::new(msg_id))) |
Closes #274
Looking for initial comments so I can wrap up this implementation. I've successfully validated reply and forward functionality.
example handler: