Catch error without panic

I'm trying to send same email to multiple users using lettre.rs, and do not want the app to panic if email could not be sent to any of the users, just need to be notified, and continue sending other emails, soI wrote this code:

use lettre::smtp::response::Response;
use lettre::smtp::error::Error;

let users = vec!("wrong@email", "correct@gmail.com");

let template = Email::builder()
       .from("myEmail@gmail.com")
       .subject("subject new")
       .html("<h1>Hi man</h1>")
       .text("message");

    let creds = Credentials::new(
        "myEmail@gmail.com".to_string(),
        "myPassword".to_string(),
    );

    let mut mailer = SmtpClient::new_simple("smtp.gmail.com")
        .unwrap()
        .credentials(creds)
        .transport();

    for user in users {
        let email = template.clone().to(user)
            .build().unwrap();

        let result: Result<Response, Error> = mailer.send(email.into());

        match result {
            Ok(_) => println!("Email sent to: {}", user),
            Err(e) => println!("Could not send to: {}, email error: {:?}", user, e)
        }
// OR
    //    if result.is_ok() {
    //        println!("Email sent to: {}", user);
    //    } else {
    //        println!("Could not send to: {}, email error: {:?}", user, result);
    //    }
}

But the app is panic and exit at the first wrong case it face, and I got this error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Envelope(InvalidEmailAddress)', src/libcore/result.rs:999:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

How can I avoid this panic?

If you don't want panics, don't use unwrap(). Use the ? operator instead:

https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html

.is_ok() is mostly useless. Typically you'd use match or if let.

1 Like

Thanks, So the panic was in .build.unwrap() not in mailer.send as I was thinking.

I could not replace the unwrap() by ? as I got an error:

 cannot use the `?` operator in a function that returns `()`

But the if let worked fine, and I changed my code to be:

    for user in users {
        if let Ok(email) = template.clone().to(user).build() {
            if let Ok(result) = mailer.send(email.into()) {
                println!("Email sent to: {}", user)
            } else {
                println!("Could not send to: {}", user)
            }
        }
        else {
            println!("Invalid Email Address: {}", user)
        }

But how can I catch the error using if let?

UPDATE
I got the error using match as below:

use lettre_email::{Email};
use lettre::smtp::authentication::Credentials;
use lettre::{SmtpClient, Transport};
use std::path::Path;

fn main() {

    let users = vec!("wrong@email", "correct@gmail.com");

    match SmtpClient::new_simple("smtp.gmail.com") {
        Err(e) => println!("Could not connect to server: {:?}", e),
        Ok(client) => {
            let mut mailer = client.credentials(Credentials::new(
            "myLOGIN@gmail.com".to_string(),
            "myPSSWD".to_string(),
             )).transport();

            match Email::builder()
                .attachment_from_file(Path::new("myfile.pdf"), None, &mime::APPLICATION_PDF)
                {
                    Err(e) => println!("Error in preparing attachment {:?}", e),
                    Ok(e_builder) => {
                        let template = e_builder
                            .from("myEMAIL@gmail.com")
                            .subject("subject new")
                            .html("<h1>Hi man</h1>")
                            .text("message");
                        
                        for user in users {
                            match template.clone().to(user).build() {
                                Err(e) => println!("error: {:?} in email: {}", e, user),
                                Ok(e) => {
                                    match mailer.send(e.into()) {
                                        Err(e) => println!("error: {:?} in emailing: {}", e, user),
                                        Ok(r) => println!("Emailing: {}, {:?}", user, r.message)
                                    }
                                }
                            }
                        }
                    }
                }
        }
    }
}

Not sure if this is the best way, or there is a better way.

1 Like

If you just want to print any error on each iteration you could box them:

for user in users {
    let res: Result<_, Box<dyn std::error::Error>> = template
        .clone()
        .to(user)
        .build()
        .map_err(Into::into)
        .and_then(|e| {
            mailer
                .send(e.into())
                .map_err(Into::into)
        });

    match res {
        Ok(_) => println!("Email sent to: {}", user),
        Err(e) => println!("Error emailing {}: {:?}", user, e),
    }
}

Or write a function to email a given user and use ? in there:

fn email_user(
    mailer: &mut SmtpTransport,
    template: &EmailBuilder,
    user: &str
) -> Result<(), Box<dyn std::error::Error>> {
    let email = template.clone().to(user).build().map_err(Into::into)?;
    mailer.send(email.into()).map_err(Into::into)
}

for user in users {
    match email_user(mailer, template, user) {
        Ok(_) => println!("Email sent to: {}", user),
        Err(e) => println!("Error emailing {}: {:?}", user, e),
    }
}

e: Fixed errors.

2 Likes

I got 2 errors:

error[E0412]: cannot find type `SmptTransport` in this scope
 --> src/main.rs:8:18
  |
8 |     mailer: &mut SmptTransport,
  |                  ^^^^^^^^^^^^^ help: a struct with a similar name exists: `SmtpTransport`

error[E0425]: cannot find value `e` in this scope
  --> src/main.rs:13:17
   |
13 |     mailer.send(e.into()).map_err(Into::into)
   |                 ^ not found in this scope


SmptTransport should be SmtpTransport, (like the error message suggests,) and e should be email.

1 Like

The correct course of action in this case is to make the function return Result<(), SomeErrorType>. The error type can be the same as the library that you used returns. If there are multiple types, the easy way out is failure::Error or Box<dyn std::error::Error>.

Try writing programs without using any unwrap() ever. Rust has enough proper error handling capabilities to make it possible.

3 Likes

Historically .unwrap() is recommended only for cases when

  1. You're 100% sure it will not fail like let url: IpAddr = "127.0.0.1".parse().unwrap();

  2. You're writing some example snippet so don't care about error case at all.

But since Rust 1.26, It's allowed to return Result in fn main() and community starts recommending to use ? in the second case, because you should do so in the real code.

Anyway, if you think some operation should not fail but not 100% sure it actually will, use .expect("some error msg") to identify the crash location in the panic message. Or you can set RUST_BACKTRACE=1 to print stack trace on panic.

3 Likes