Skip to content
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

Emails with attachments are not sent on windows #894

Open
Lexharden opened this issue Aug 7, 2023 · 1 comment
Open

Emails with attachments are not sent on windows #894

Lexharden opened this issue Aug 7, 2023 · 1 comment

Comments

@Lexharden
Copy link

Lexharden commented Aug 7, 2023

I have been developing a script that monitors a folder if there are new files, when it detects a new one, it uploads it through an api and proceeds to send it by email via smtp gmail. The strange thing is that this only happens with windows, in linux it works perfectly. It is the same code and same SMTP credentials.

  • Cargo 1.71.0
  • Lettre version 0.10
  • OS Windows 10

I already tried opening the ports in windows. I have tried the example that comes in the documentation and it works without problems, but I created my own function to send the mail and it doesn't work anymore.
It is worth mentioning that I have debugged the code and everything is fine until it reaches the last line of email_sender.rs, everything loads correctly and nothing else happens. It does not throw any error, nothing in the terminal

  // Send the email
     match mailer.send(&email) {
         Ok(_) => Ok(println!("Mail sent successfully!")),
         Err(e) => panic!("Error sending mail: {:?}", e),
     }

main.rs

mod email_sender;
use std::fs;
use dotenv::dotenv;
use std::env;
use std::path::Path;
use std::collections::HashSet;
use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result, EventKind, Event};
use reqwest;



fn load_sent_files() -> HashSet<String> {
    if let Ok(content) = fs::read_to_string("sent_files.txt") {
        content.lines().map(|line| line.to_string()).collect()
    } else {
        HashSet::new()
    }
}

fn save_sent_files(sent_files: &HashSet<String>) {
    let content: Vec<String> = sent_files.iter().cloned().collect();
    if let Err(e) = fs::write("sent_files.txt", content.join("\n")) {
        println!("Error al guardar los archivos enviados: {:?}", e);
    }
}

fn upload_file(file_path: &Path, sent_files: &mut HashSet<String>) {
    let file_name = file_path.file_name().unwrap().to_str().unwrap().to_string();

    if sent_files.contains(&file_name) {
        println!("Archivo {} ya ha sido enviado anteriormente.", file_name);
        return;
    }

    let client = reqwest::blocking::Client::new();
    let url = "http://localhost:8080/upload"; // Ajusta esto a la URL de tu API

    let form = reqwest::blocking::multipart::Form::new()
        .file("file", file_path)
        .unwrap(); // Puedes manejar este error de una mejor manera si lo deseas

    let response = client.post(url)
        .multipart(form)
        .send();

    match response {
        Ok(resp) => {
            if resp.status().is_success() {
                // Obtiene el cuerpo de la respuesta y lo muestra.
                let response_body = resp.text().unwrap_or_else(|_| String::from("Respuesta vacía"));
                println!("Respuesta del servidor: {}", response_body);
                println!("Archivo enviado con éxito: {}", file_name);

                // Agrega el nombre del archivo al conjunto de archivos enviados
                sent_files.insert(file_name.clone());
                save_sent_files(&sent_files);  // Guarda el conjunto actualizado en el archivo

                // Envía el archivo adjunto por correo electrónico
                let recipient_email = env::var("RECIPIENT_EMAIL").expect("RECIPIENT_EMAIL not set");
                if let Err(e) = email_sender::send_email_with_attachment(file_path, &recipient_email,&file_name) {
                    println!("Error al enviar el correo electrónico: {:?}", e);
                }
            } else {
                println!("Error al enviar el archivo: {:?}", resp.text().unwrap_or_else(|_| String::from("Error desconocido")));
            }
        }
        Err(e) => {
            println!("Error en la solicitud: {:?}", e);
        }
    }
}

fn main() -> Result<()> {
    println!("Monitor de carpeta iniciado.");
    // Carga las variables de entorno desde el archivo .env
    dotenv().ok();

    let mut sent_files = load_sent_files();  // Carga el conjunto de archivos enviados al iniciar

    let mut watcher: RecommendedWatcher = notify::recommended_watcher(move |res: Result<Event>| {
        match res {
            Ok(event) => {
                if let EventKind::Create(_) = event.kind {
                    let file_path = &event.paths[0];
                    println!("Archivo nuevo detectado: {:?}", file_path);
                    upload_file(file_path, &mut sent_files); // Pasa el conjunto a la función upload_file
                }
            }
            Err(e) => println!("Error al monitorear: {:?}", e),
        }
    })?;
    let folder_path = env::var("FOLDER_PATH").expect("FOLDER_PATH not set");
    watcher.watch(Path::new(&folder_path), RecursiveMode::Recursive)?;
    std::thread::park();
    Ok(())
}

email_sender.rs

use std::path::Path;
use std::error::Error;
use lettre::message::{header, Message};
use lettre::message::{header::ContentType, MultiPart, SinglePart, Attachment};
use lettre::transport::smtp::authentication::Credentials;
use lettre::{SmtpTransport, Transport};
use anyhow::Result;
use std::{fs, env};

pub(crate) fn send_email_with_attachment(file_path: &Path, recipient_email: &str, file_name: &str) -> Result<(), Box<dyn Error>> {
    let file_name = file_name.to_string();
    let file_content = fs::read(file_path)?;

    // Use the correct ContentType for MiniSEED files
    let content_type = ContentType::parse("application/vnd.fdsn.mseed").unwrap();

    // Create a single part for the attachment
    let attachment = Attachment::new(file_name.clone()).body(file_content, content_type);

    let message = String::from("Envio MSeed");

    let email = Message::builder()
        .from("[email protected]".parse()?)
        .to(recipient_email.parse()?)
        .subject("Envio Miniseed")
        .multipart(
            MultiPart::mixed()
                .singlepart(
                    SinglePart::builder()
                        .header(header::ContentType::TEXT_PLAIN)
                        .body(String::from(message)), 
                )
                .singlepart(attachment),
        )?;

    let smtp_username = env::var("SMTP_USERNAME").expect("SMTP_USERNAME not set");
    let smtp_password = env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD not set");
    let smtp_host = env::var("SMTP_HOST").expect("SMTP_HOST not set");

    let creds = Credentials::new(smtp_username, smtp_password);

    // Open a remote connection to gmail
    let mailer = SmtpTransport::relay(&smtp_host)?
        .credentials(creds)
        .build();

    // Send the email
    match mailer.send(&email) {
        Ok(_) => Ok(println!("Correo enviado exitosamente!")),
        Err(e) => panic!("Error al enviar correo: {:?}", e),
    }
}

Cargo.toml

[dependencies]
notify = { version = "6.0.1", features = ["serde"] }
reqwest = { version = "0.11", features = ["json", "blocking", "multipart"] }
tokio = { version = "1", features = ["full"] }
log = "0.4.19"
lettre = "0.10"
mime = "0.3.17"
dotenv = "0.15.0"
anyhow = "1.0.57"
@Niedzwiedzw
Copy link

  1. notify is linux-only API AFAIK
  2. you are probably using (default) native certificates, you could try building it with rustls (look at lettre's Cargo.toml to find proper features, I haven't tested that part yet but in my code this compiles:
lettre = { version = "0.10.4", default-features = false, features = [
  "tokio1",
  "tracing",
  "webpki-roots",
  "rustls",
  "tokio1-rustls-tls",
] }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants