Skip to content

feat(EmailWorkers): Implement email worker functionality #715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nudded
Copy link

@nudded nudded commented Mar 15, 2025

Closes #274

Looking for initial comments so I can wrap up this implementation. I've successfully validated reply and forward functionality.

example handler:

use uuid::Uuid;
use worker::*;

#[event(email)]
async fn main(message: EmailMessage, _env: Env, _ctx: Context) -> Result<()> {
    let message_id = message.headers().get("Message-ID")?.unwrap();

    let msg = format!(
        "From: Cloudflare bot <{}>
To: Toon <{}>
In-Reply-To: {}
Message-ID: <{}@redacted.com>
Subject: Email well received!

I've parsed the mail!

",
        message.to_email(),
        message.from_email(),
        message_id,
        Uuid::new_v4()
    );

    let new_message = EmailMessage::new(&message.to_email(), &message.from_email(), &msg)?;

    message.reply(new_message).await?;
    Ok(())
}

@DougAnderson444
Copy link

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

@DougAnderson444
Copy link

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?

@DougAnderson444
Copy link

Actually, on second thought, maybe msg should just be left a a string and if the user wants to use a build helper, it can exist in userland as a separate crate.

@nudded
Copy link
Author

nudded commented Mar 17, 2025

Actually, on second thought, maybe msg should just be left a a string and if the user wants to use a build helper, it can exist in userland as a separate crate.

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)

@DougAnderson444
Copy link

@nudded
Copy link
Author

nudded commented Mar 18, 2025

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 Date header, but did not want to spend much more time debugging)

@nudded
Copy link
Author

nudded commented Mar 25, 2025

@zebp If you have some time for a review :)

@jeholliday
Copy link

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().

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.

[Feature] Add email worker support
3 participants