application-mailer
Version:
tool to send bulk applications
163 lines (142 loc) • 4.58 kB
JavaScript
const nodemailer = require("nodemailer");
const fs = require("fs");
const path = require("path");
const csv = require("csv-parser");
class mailer {
constructor(myemail, password) {
this.myemail = myemail;
this.password = password;
this.transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: this.myemail,
pass: this.password, // Use env vars or app password
},
});
this.requirements = [];
}
addInfo(resumePath, coverLetterPath) {
this.resumePath = resumePath;
this.coverLetterPath = coverLetterPath;
this.requirements.push("coverLetter", "resumePath");
}
addSubject(subject) {
this.subject = subject;
this.requirements.push("subject");
}
addCSV(csvPath, csvHasHeaders = true, emailColumnName = "Email") {
this.csvPath = csvPath;
this.csvHasHeaders = csvHasHeaders;
this.emailColumnName = emailColumnName;
this.requirements.push("csv");
}
Errors() {
if (this.requirements.length === 0) {
console.error("No requirements found");
return true;
}
const required = ["subject", "csv", "resumePath", "coverLetter"];
const missing = required.filter((r) => !this.requirements.includes(r));
if (missing.length > 0) {
console.error("Missing requirements:", missing.join(", "));
return true;
}
return false;
}
fileExists(filePath) {
try {
return fs.existsSync(filePath);
} catch (err) {
return false;
}
}
async sendEmail(email, current, total) {
try {
const coverLetter = fs.readFileSync(this.coverLetterPath, "utf-8");
const mailOptions = {
from: this.myemail,
to: email,
subject: this.subject,
text: coverLetter,
attachments: [
{
filename: "Resume.pdf",
path: this.resumePath,
},
],
};
const info = await this.transporter.sendMail(mailOptions);
console.log(`✓ [${current}/${total}] Email sent to ${email}`);
return true;
} catch (error) {
console.error(
`✗ [${current}/${total}] Error sending to ${email}:`,
error.message
);
return false;
}
}
async processApplications() {
if (!this.fileExists(this.resumePath)) {
console.error(`✗ Error: Resume file not found at ${this.resumePath}`);
return;
}
if (!this.fileExists(this.csvPath)) {
console.error(`✗ Error: CSV file not found at ${this.csvPath}`);
console.log(`Make sure your CSV file is located at: ${this.csvPath}`);
return;
}
console.log(`Starting to process applications from: ${this.csvPath}`);
const emails = [];
return new Promise((resolve, reject) => {
fs.createReadStream(this.csvPath)
.on("error", (error) => {
console.error(`✗ Error reading CSV file: ${error.message}`);
reject(error);
})
.pipe(csv({ headers: this.csvHasHeaders ? undefined : false }))
.on("data", (row) => {
let email;
if (this.csvHasHeaders === false) {
email =
typeof row === "string"
? row.trim()
: Object.values(row)[0].trim();
} else {
email = row[this.emailColumnName]?.trim();
}
if (email && email.includes("@")) {
emails.push(email);
} else {
console.log(`Invalid email format found: ${email}`);
}
})
.on("end", async () => {
console.log(`Found ${emails.length} valid email addresses`);
if (emails.length === 0) {
console.log(
"No valid emails found. Please check your CSV file format."
);
resolve();
return;
}
let successCount = 0;
const totalEmails = emails.length;
console.log(`\nStarting email sending process...`);
for (let i = 0; i < emails.length; i++) {
const email = emails[i];
const result = await this.sendEmail(email, i + 1, totalEmails);
if (result) successCount++;
if (i < emails.length - 1) {
await new Promise((res) => setTimeout(res, 2000));
}
}
console.log(`\nProcess completed!`);
console.log(`Successfully sent: ${successCount}/${totalEmails}`);
console.log(`Failed: ${totalEmails - successCount}`);
resolve();
});
});
}
}
module.exports = mailer;