node library to send emails

This commit is contained in:
inder-dml 2024-09-30 17:12:10 +05:30
commit 9c836745bb
7 changed files with 307 additions and 0 deletions

4
.gitignore vendored Executable file
View file

@ -0,0 +1,4 @@
node_modules/
npm-debug.log
.DS_Store
package-lock.json

100
README.md Normal file
View file

@ -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
Heres 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: '<strong>This is a bold HTML message.</strong>',
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)

5
index.js Normal file
View file

@ -0,0 +1,5 @@
import Transporter from './lib/transporter.js';
export function createTransport(options) {
return new Transporter(options);
}

36
lib/email.js Normal file
View file

@ -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;

109
lib/smtp.js Normal file
View file

@ -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;

33
lib/transporter.js Normal file
View file

@ -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;

20
package.json Normal file
View file

@ -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"
}
}