commit 9c836745bb937de55bde33d646978e3a0650f71a Author: inder-dml Date: Mon Sep 30 17:12:10 2024 +0530 node library to send emails diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..2db4110 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +npm-debug.log +.DS_Store +package-lock.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3cf374 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# digi-sendzi βœ‰οΈ + +digi-sendzi is a simple yet powerful SMTP email-sending library built in JavaScript. It provides an easy-to-use interface for sending emails, supporting plain text, HTML content, and attachments. + +## Features 🌟 + +- **Send emails using SMTP protocol**: Reliable and secure email delivery. +- **Support for multipart emails**: Easily send both text and HTML formats. +- **Attach files to emails**: Include attachments for richer communication. +- **Basic authentication support**: Use your username and password for authentication. +- **Simple error handling**: Manage errors with ease. + +## Installation πŸ› οΈ + +To install digi-sendzi, you can use npm: + +```bash +npm install digi-sendzi +``` +## Usage πŸ“§ +Basic Example +Here’s a simple example of how to use digi-sendzi to send an email: + +```javascript +import { createTransport } from 'digi-sendzi'; + +const transporter = createTransport({ + host: 'smtp.example.com', + port: 587, + auth: { + user: 'your-email@example.com', + pass: 'your-email-password' + } +}); + +const mailOptions = { + from: 'your-email@example.com', + to: 'recipient@example.com', + subject: 'Hello from digi-sendzi! 🌍', + text: 'This is a plain text message.', + html: 'This is a bold HTML message.', + attachments: [ + { + filename: 'example.txt', + content: 'Hello, this is the content of the file!', + contentType: 'text/plain' + } + ] +}; + +transporter.sendMail(mailOptions) + .then(() => { + console.log('Email sent successfully! πŸŽ‰'); + }) + .catch((error) => { + console.error('Error sending email:', error.message); + }); +``` +## API Reference πŸ“š + +### createTransport(options) +Creates a new Transporter instance. + +**options:** An object containing the following properties: +- **host** (string): The SMTP server host. +- **port** (number, optional): The port to connect to (default is 25). +- **auth** (object, optional): An object containing `user` and `pass` for authentication. + +### Transporter.sendMail(mailOptions) +Sends an email with the specified options. + +**mailOptions:** An object containing the following properties: +- **from** (string): Sender's email address (required). +- **to** (string): Recipient's email address (required). +- **subject** (string): Email subject (required). +- **text** (string): Plain text body of the email (required). +- **html** (string, optional): HTML body of the email. +- **attachments** (array, optional): An array of attachment objects with `filename`, `content`, and `contentType`. + +## Error Handling ⚠️ +digi-sendzi provides basic error handling. If any required fields are missing or an error occurs during the SMTP connection or email sending, an error will be thrown. You can catch these errors using a `.catch()` block when sending an email. + +## Development πŸ’» +To run tests, ensure you have Jest installed as a dev dependency, and run: + +```bash +npm test +``` + +## License πŸ“ +This project is licensed under the ISC License. + +## Author ✍️ +DigiMantra + +## Links + +* **GitHub**: [digi-sendzi Repository](https://git.digimantra.com/inder-dml/digi-sendzi.git) +* **NPM**: [digi-sendzi Package](https://www.npmjs.com/package/digi-sendzi) + diff --git a/index.js b/index.js new file mode 100644 index 0000000..69391dc --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +import Transporter from './lib/transporter.js'; + +export function createTransport(options) { + return new Transporter(options); +} diff --git a/lib/email.js b/lib/email.js new file mode 100644 index 0000000..1c50d61 --- /dev/null +++ b/lib/email.js @@ -0,0 +1,36 @@ +class Email { + constructor(from, to, subject, body, html = null, attachments = []) { + this.from = from; + this.to = to; + this.subject = subject; + this.body = body; + this.html = html; // Optional HTML version + this.attachments = attachments; + this.boundary = `----Boundary${Math.random().toString(36).substring(2)}`; // Unique boundary string + } + + format() { + let emailString = `From: ${this.from}\r\nTo: ${this.to}\r\nSubject: ${this.subject}\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary="${this.boundary}"\r\n\r\n`; + + // Add body as text or both text and HTML parts + if (this.body) { + emailString += `--${this.boundary}\r\nContent-Type: text/plain; charset="UTF-8"\r\n\r\n${this.body}\r\n`; + } + if (this.html) { + emailString += `--${this.boundary}\r\nContent-Type: text/html; charset="UTF-8"\r\n\r\n${this.html}\r\n`; + } + + // Add attachments if any + if (this.attachments.length > 0) { + this.attachments.forEach(attachment => { + emailString += `--${this.boundary}\r\nContent-Disposition: attachment; filename="${attachment.filename}"\r\nContent-Type: ${attachment.contentType}; name="${attachment.filename}"\r\nContent-Transfer-Encoding: base64\r\n\r\n${attachment.content}\r\n`; + }); + } + + // Close the MIME message + emailString += `--${this.boundary}--\r\n`; + return emailString; + } +} + +export default Email; diff --git a/lib/smtp.js b/lib/smtp.js new file mode 100644 index 0000000..e4c866d --- /dev/null +++ b/lib/smtp.js @@ -0,0 +1,109 @@ +import { connect } from 'tls'; // For SSL and STARTTLS +import { Socket } from 'net'; // For non-encrypted connections (before STARTTLS) + +class SMTPClient { + constructor(host, port = 587, auth) { + this.host = host; + this.port = port; + this.auth = auth; + this.socket = null; + } + + connect() { + return new Promise((resolve, reject) => { + if (this.port === 465) { + // SSL connection (SMTPS) + this.socket = connect({ host: this.host, port: this.port, rejectUnauthorized: false }, () => { + console.log(`Connected to SMTP server with SSL at ${this.host}:${this.port}`); + resolve(); + }); + } else if (this.port === 587) { + // Non-encrypted connection, STARTTLS will be applied later + this.socket = new Socket(); + this.socket.connect(this.port, this.host, () => { + console.log(`Connected to SMTP server at ${this.host}:${this.port}`); + resolve(); + }); + } else { + // Plain non-encrypted connection (used for ports like 2525) + this.socket = new Socket(); + this.socket.connect(this.port, this.host, () => { + console.log(`Connected to SMTP server at ${this.host}:${this.port} (No SSL/TLS)`); + resolve(); + }); + } + + this.socket.on('error', (err) => { + reject(err); + }); + }); + } + + async sendEmail(from, to, subject, body) { + try { + if (this.port === 587) { + // If using STARTTLS + await this.sendCommand(`EHLO ${this.host}`); + await this.sendCommand(`STARTTLS`); + this.upgradeToTLS(); // Upgrade connection to TLS after STARTTLS + } + + await this.sendCommand(`EHLO ${this.host}`); // Send EHLO again after STARTTLS or for SSL + await this.sendCommand(`AUTH LOGIN`); + await this.sendAuth(); + await this.sendCommand(`MAIL FROM:<${from}>`); + await this.sendCommand(`RCPT TO:<${to}>`); + await this.sendCommand('DATA'); + await this.sendCommand(`${body}\r\n.`); + await this.sendCommand('QUIT'); + } catch (error) { + throw new Error(`Error sending email: ${error.message}`); + } + } + + async sendAuth() { + const base64User = Buffer.from(this.auth.user).toString('base64'); + const base64Pass = Buffer.from(this.auth.pass).toString('base64'); + await this.sendCommand(base64User); + return await this.sendCommand(base64Pass); + } + + sendCommand(command) { + return new Promise((resolve, reject) => { + this.socket.write(`${command}\r\n`, (err) => { + if (err) return reject(err); + + this.socket.once('data', (data) => { + const response = data.toString(); + console.log('SMTP Response:', response); + + if (response.startsWith('250') || response.startsWith('354') || response.startsWith('235')) { + resolve(response); + } else if (response.startsWith('334')) { + resolve(); // Continue authentication + } else if (response.startsWith('221')) { + console.log('SMTP session closed successfully.'); + resolve(response); + } else if (response.startsWith('220')) { + resolve(); // Initial greeting + } else { + console.error('SMTP Error:', response); + reject(new Error(`SMTP Error: ${response}`)); + } + }); + }); + }); + } + + // Upgrade the current connection to TLS (STARTTLS mode) + upgradeToTLS() { + this.socket = connect({ + socket: this.socket, // Upgrade the existing socket to TLS + rejectUnauthorized: false + }, () => { + console.log(`Connection upgraded to TLS`); + }); + } +} + +export default SMTPClient; diff --git a/lib/transporter.js b/lib/transporter.js new file mode 100644 index 0000000..980744f --- /dev/null +++ b/lib/transporter.js @@ -0,0 +1,33 @@ +import SMTPClient from './smtp.js'; +import Email from './email.js'; + +class Transporter { + constructor(options) { + this.host = options.host; + this.port = options.port || 25; + this.auth = options.auth; + + this.client = new SMTPClient(this.host, this.port, this.auth); + } + + async sendMail(mailOptions) { + const { from, to, subject, text, html, attachments } = mailOptions; + + // Basic validation + if (!from || !to || !subject) { + throw new Error('Missing required email fields: from, to, subject'); + } + + try { + await this.client.connect(); + const email = new Email(from, to, subject, text, html, attachments); + await this.client.sendEmail(from, to, subject, email.format()); + console.log('Email sent successfully!'); + } catch (error) { + console.error('Error sending email:', error.message); + throw new Error(`Failed to send email: ${error.message}`); // Propagate error to API + } + } +} + +export default Transporter; diff --git a/package.json b/package.json new file mode 100644 index 0000000..864f686 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "digi-sendzi", + "version": "1.0.0", + "description": "A simple SMTP email sending library.", + "main": "index.js", + "type": "module", + "scripts": { + "test": "jest" + }, + "keywords": [ + "email", + "smtp", + "mailer" + ], + "author": "DigiMantra", + "license": "ISC", + "devDependencies": { + "jest": "^29.7.0" + } +}