Is it safe to use a Mutex for an SMTP client connection?

Hello!

For my Actix-Web server, there is a request handler send_verification_code that sends a verification code to a user and then redirects them to another page where they'll need to enter said code.

Initially, I made it such that during each request, an SMTP client connection would be made, and then I would work on sending the email. However, I noticed that the total time for both creating the connection and then sending an email was pretty long, so there would have been a delay in redirecting the user.

As a result, I placed that connection into the application's data wrapper as an Arc<Mutex<>>, and I would then call it as a parameter in send_verification_code - thankfully, that did split the time by half, since the time taken to make the connection would have already been offloaded when the server was starting up:

// Me calling the SMTP client connection
state.smtp_client.lock().await.send(message_builder).await

But then I started to worry about something.

From what I understand so far, the lock method may delay a process until the wrapped resource is available. Consequently, although Kubernetes may help with scaling up and stuff, there could be cases where some threads are trying to access that connection, but due to another currently using it (which could probably be exacerbated if there is a user at the other side of the world), things could get pretty slow real quick.

I'm not sure if my concern is valid (ChatGPT said it was, but I was still skeptical), but I thought one way of getting around this was to first redirect the user to the other page and then send them the verification code. (I would then no longer place the connection in the application's data wrapper, and thus direct that waiting time once the user is already redirected)

Any insights would be appreciated, thanks.

Something to note here is that the connection may be broken by the time you go to use it (server failure, network reconfiguration, idle timeout). You need to handle that case too.

This is certainly something you should consider whenever you are using a mutex-protected resource from multiple threads/tasks. However, where the recipient is located has nothing to do with it; SMTP is a store-and-forward protocol, meaning that your mail server accepts the message and acknowledges it to you well before it is delivered to the recipient. (If you're trying to connect directly to recipients’ mail servers, don't do that; that'll just get your messages discarded as presumed spam.)

There are slightly more efficient and reliable ways to use a single connection, however. In particular, you should consider the “actor pattern”: have a task (the actor) which owns the SMTP connection and the receiving end of a channel, and reads from the channel and writes messages to the SMTP connection. This means that the request handler spends no time waiting for SMTP (unless the mail actor is not keeping up), and the mail actor is easily in a position to handle reconnecting if the SMTP connection is lost.

Alice Ryhl of the Tokio project wrote an article introducing how to write actors in Tokio.

3 Likes

More likely you will migrate from a direct e-mail sending to just place an e-mail content in a file in the e-mail out directory. So you will need Arc for just generating a unique e-mail id. An actual e-mail sender can be a single thread, or multi threads based. But, it will be more likely not your worry.

It seems you already have the solution. In my experience, this is how almost all verification code mechanisms work - I get redirected immediately, then the email comes later.