Send Email with JavaScript: Every Method Developers Should Know

Using JavaScript to Send Email: Every Method Developers Should Know
Vitalii Piddubnyi Vitalii Piddubnyi 18 march 2026, 05:41 1997
For experts

Email-sending functionality, such as password resets, order confirmations, contact form submissions, and welcome messages, is very common in modern web applications. It comes up in almost every web app development project.

Since JavaScript powers most of the modern web, the natural question is: can you use JavaScript to send an email directly?

The short answer is yes, but with caveats. How you send an email with JavaScript depends entirely on where your code runs. See, JavaScript code can be run either inside your browser or directly on the web server. Browser-based JavaScript has very limited abilities when it comes to email (although it still offers a couple of useful options). Server-side JavaScript, however, can handle everything email-related from simple transactional messages to bulk campaigns.

This guide walks through every available method for sending emails using JavaScript, starting from the simplest client-side code and working up to production-grade server-side implementations. Whether you need a quick contact form or a reliable backend email system, you'll find the right approach here.

Prerequisites

Before getting started with this guide, ensure you have the following:

  • Node.js v18+ installed. Download the latest version here;
  • Basic knowledge of HTML, CSS, and JavaScript;
  • An SMTP account (e.g., registered at UniOne).

Can You Send Emails Directly with JavaScript?

Not in the way you might expect.

JavaScript running in the browser doesn't have direct access to network sockets. Browsers were never designed to speak SMTP (Simple Mail Transfer Protocol), the standard protocol that mail servers use to send and receive messages. There's no built-in sendEmail() function in the browser's JavaScript engine, and there never will be. It's a fundamental security boundary.

Think about what would happen if any website could silently fire off emails from your browser. Every sketchy page on the internet would turn your machine into a spam cannon. That's precisely why browsers lack SMTP connections.

So what are the options?

The split comes down to client-side vs. server-side approaches. Client-side methods work within the browser's constraints; they either delegate the task to the user's default email client or route requests through a third-party service. Server-side methods run in a backend environment like Node.js, where you have full control over SMTP connections, authentication, and delivery logic.

Here's a quick comparison to frame the rest of this guide:

 

Client-Side

Server-Side

Where it runs

User's browser

Server backend

SMTP access

No

Yes

Credential security

Poor, can be exposed in the source code

Good

Delivery control

None or minimal

Full (retries, logging, tracking)

Best for

Prototypes, contact forms

Production apps, transactional email

If you're building anything that needs to send email reliably and at scale, server-side is the way to go. But client-side methods still have their place, especially during prototyping or when you just need a basic "email us" link.

Let's start with the client-side options.

Client-Side Methods for Sending Emails in JavaScript

Client-side email sending is limited by design. You're working within the browser’s sandbox, which means you're either handing off to the user's default mail app or piping data through an external service.

Thetwo main approaches worth covering here are the mailto: links and EmailJS.

Sending Emails Using mailto:

The mailto: protocol is the oldest trick in the book for sending email from a web page, only it doesn't actually send an email. Instead, it opens the user's default email client with pre-filled fields whenever they click the email hyperlink.

Here's the basic HTML syntax example:

<a href="mailto:support@example.com?subject=Hello&body=I%20have%20a%20question>
  Email Us
</a>

You can embed this HTML into the webpage where you’d want users to send the email from. 

So, when a user clicks the link, they are automatically redirected to their default email client. It opens with pre-filled To, Subject and Body fields.

Note: The %20 in the original string represents spaces. All special characters that are not allowed in URLs should be similarly urlencoded.

You can also trigger mailto: programmatically with JavaScript, for example, by setting the following function as onclick event handler for a button:

function openEmail() {
  const to = "support@example.com";
  const subject = encodeURIComponent("Question about my order");
  const body = encodeURIComponent("Hi, I'd like to ask about order #12345.");
 
  window.location.href = `mailto:${to}?subject=${subject}&body=${body}`;
}

You can pre-fill the to, subject, body, cc, and bcc fields. The user still has to hit "Send" in their mail client, which means you're depending on them to follow through.

For a basic "Contact Us" button on a landing page, this works fine. 

Limitations of mailto: for Production Use

While mailto: is simple to set up, it falls apart quickly for anything beyond the basics:

  • Depends on the user's mail client: If the user doesn't have a default email client configured (which is increasingly common, especially on mobile), the link does nothing. Or worse, it opens an app they've never set up.
  • No automation: You can't send emails programmatically in response to events like a form submission, a completed purchase, or a password reset. Everything requires manual user action.
  • No HTML formatting. The predefined body field only supports plain text. Nothing fancy at all.

Sending Emails using EmailJS

EmailJS is a service that enables sending emails directly from client-side JavaScript without a backend server. No magic involved though: EmailJS simply provides backend functionality, so you don’t need to implement it by yourself. You include a script tag in your webpage code, initialize it with your public key, and call emailjs.send(), and pass in your service ID, template ID, and message details. The downside is that you need to share your SMTP credentials with a third party.

To send emails via EmailJS, you need a dedicated SMTP server. You may either use your own or the one provided by a mail service like UniOne.

Follow these steps to get started with UniOne’s SMTP relay service:

  1. Sign up for a free UniOne account here.
  2. Add and verify your domain name. To learn more, refer to our guide on Setting up the domain’s DNS records. Alternatively, as a new user, you can use the free, pre-verified sandbox domain, which is a temporary domain for testing your email sending functionality before setting up your production domain.
  3. Navigate to Settings -> SMTP Configuration to get your SMTP credentials for sending emails.

Now that you have your SMTP credentials ready, you can start sending emails with EmailJS. Follow the steps below:

Step 1: Sign up for an EmailJS account and create an SMTP server service. To get started:

  1. Sign up for a free EmailJS account here.
  2. Click Email Services on the left sidebar.
  3. Click Add New Service.
  4. Select “SMTP server” under Personal Services options.
  5. Enter your SMTP credentials. 
  6. Click Create Service.

Note: Uncheck the "Send test email..." checkbox before creating the service. 

Write down the Service ID provided to you; you will use it in your code. 

Sending Emails using EmailJS - SMTP server

Step 2: Navigate to Email Templates -> Create New Template -> Create Template. In the template editor, set the subject to {{subject}}, the body to {{message}}, and the From Email to your UniOne free sandbox domain.

Sending Emails using EmailJS

Write down the Template ID provided to you; you will use it in your code. 

Sending Emails using EmailJS - Create Template

Next, create a new HTML file called index.html or edit an existing HTML file, particularly the one connected to your homepage.

Next, add the code snippet below to the <head> tag:

<head>
    <script src="https://cdn.jsdelivr.net/npm/@emailjs/browser@4/dist/email.min.js "></script>
</head>

Then, initialize EmailJS with your public key and create a function to send the email:

emailjs.init("YOUR_PUBLIC_KEY");

function sendEmail() {
    emailjs.send("YOUR_SERVICE_ID", "YOUR_TEMPLATE_ID", {
        to_email: "recipient@example.com",
        from_name: "Your Name",
        subject: "Test Email from EmailJS",
        message: "This email was sent directly from the browser."
    })
    .then(function () {
        alert("Email sent successfully!");
    })
    .catch(function (error) {
        alert("Failed: " + JSON.stringify(error));
    });
}

The code above uses EmailJS to send an email directly from the browser. We first initialize the library with our public key using emailjs.init(). The sendEmail() function then uses emailjs.send() to trigger email sending. Once the email is sent, the .then() callback triggers an alert to confirm the result. The .catch() callback handles any errors.

Note: To get your EmailJS public key, navigate to Account in the left sidebar.

You can wire this up to a form button:

<body>
    <form method="post">
        <input type="button" value="Send Email" onclick="sendEmail()" />
    </form>
</body>

EmailJS routes your email through your UniOne SMTP service, which handles the actual delivery to the recipient's mail server.

The full code snippet looks like this:

<!DOCTYPE html>
<html>

<head>
    <title>EmailJS Email Test</title>
    <script src="https://cdn.jsdelivr.net/npm/@emailjs/browser@4/dist/email.min.js"></script>
</head>

<body>
    <form method="post">
        <input type="button" value="Send Email" onclick="sendEmail()" />
    </form>

    <script type="text/javascript">
        emailjs.init("YOUR_PUBLIC_KEY");

        function sendEmail() {
            emailjs.send("YOUR_SERVICE_ID", "YOUR_TEMPLATE_ID", {
                to_email: "recipient@example.com",
                from_name: "Your Name",
                subject: "Test Email from EmailJS",
                message: "This email was sent from the browser!",
            })
            .then(function () {
                alert("Email sent successfully!");
            })
            .catch(function (error) {
                alert("Failed: " + JSON.stringify(error));
            });
        }
    </script>
</body>

</html>

Now, when you open the HTML file in your browser, you'll see a form with one “Send Email” button. When you click the button, you should see a success alert in your browser. Check the email inbox of your specified recipient.

Sending Emails using EmailJSand UniOne

Security Risks and Limitations of EmailJS

Here are the key security risks and limitations of EmailJS:

  • Code and data exposure: Since EmailJS operates on the frontend, your code, including the service ID, is exposed. Anyone can open DevTools and see it. However, someone who copies your keys will only be able to send your templates with your content. They won't be able to send custom emails with their own content, which makes it uninteresting for spammers. 
  • Bot vulnerability: Because EmailJS is frontend-based, it is vulnerable to spam bots submitting forms and triggering emails. Without protection, bots can hit your form and burn through your email quota.
  • Template-based: It's template-based, meaning you design your email structure in the dashboard. You can't dynamically build custom email content the way you can with a backend solution like Nodemailer.

Server-Side Methods for Sending Emails in JavaScript

This is where JavaScript email sending gets serious. When you move to the server side, typically powered by Node.js, you gain direct access to SMTP connections, secure credential storage, and the full range of email features, including HTML templates, attachments, CC/BCC, retry logic, delivery tracking, and more.

Server-side email sending works like this: your frontend (a web page or a mobile app) makes an HTTP request to your backend. The backend processes the request, constructs the email, and communicates with an SMTP server or Email API to deliver it. Credentials never leave your server, and the user never sees them.

There are two main approaches on the server side:

  • SMTP with a library like Nodemailer: You connect directly to an SMTP server and send messages through the protocol. This gives you maximum control and works with any SMTP provider.
  • Email API services: You make HTTP requests to a provider's API (like UniOne, SendGrid, or Mailgun) to send emails. The provider handles email sending, deliverability, and infrastructure on their end.

Both approaches are production-ready. The choice between them depends on your project's complexity, volume, and how much infrastructure you want to manage yourself.

Send Emails Using Node.js and Nodemailer

Nodemailer is the go-to library for sending emails in Node.js. It's been around since 2010, has zero dependencies as of version 7.x, and supports everything you'd need in production: SMTP connections, HTML content, file attachments, embedded images, DKIM signing, and OAuth2 authentication.

If you've worked with Node.js, you've probably seen Nodemailer mentioned, and there's a good reason. It's the most mature and widely used email library in the JavaScript ecosystem.

For a deeper walkthrough specifically focused on the Node.js side, you can also check out this guide on how to Send Email with Node.js.

Let's build out a working setup from scratch.

The first step is to open your terminal and create a new project folder using the commands below:

mkdir email-sender && cd email-sender
npm init -y
npm install nodemailer

Next, create a file called sendEmail.js and add the following code:

const nodemailer = require("nodemailer");

// Create a reusable transporter with your SMTP credentials
const transporter = nodemailer.createTransport({
  host: "smtp.eu1.unione.io", // or smtp.us1.unione.io
  port: 587,
  secure: false, // true for port 465, false for 587
  auth: {
    user: "your-unione-username",
    pass: "your-unione-password",
  },
});

// Define the email content
const mailOptions = {
  from: 'your@email',
  to: "recipient@example.com",
  subject: "Welcome to Our Platform",
  text: "Thanks for signing up! We're glad to have you.",
};

// Send the email
async function sendEmail() {
  try {
    const info = await transporter.sendMail(mailOptions);
    console.log("Email sent successfully:", info.messageId);
  } catch (error) {
    console.error("Failed to send email:", error.message);
  }
}

sendEmail();

The code above uses Nodemailer to send an email from a Node.js server. First, we import the nodemailer package and create a transporter using nodemailer.createTransport(), which holds your SMTP connection details that include the host, port, and authentication credentials for your UniOne SMTP server. 

Next, we define a mailOptions object that specifies the sender, recipient, subject line, and email body. Finally, the sendEmail() function calls transporter.sendMail(mailOptions) to send the email, wrapped in a try/catch block so that a success is logged to the console if the email goes through, or an error is logged if something goes wrong.

Next, run the script using the command below:

node sendEmail.js

If your SMTP credentials are correct and the server is reachable, you'll see the message ID logged in your terminal as shown below:

Send Emails Using Node.js and Nodemailer - SMTP server

Next, check the email inbox. You should see an email like this:

Send Emails Using Node.js and Nodemailer

Send HTML Emails with JavaScript

Plain text emails work for simple notifications, but most production emails leverage HTML for formatting, branding, and structure. Nodemailer makes this straightforward. You just add an html property to your message options.

const mailOptions = {
  from: '"Your App" <noreply@yourdomain.com>',
  to: "recipient@example.com",
  subject: "Your Monthly Report Is Ready",
  text: "Your report is ready. View it in a modern email client for the full experience.",
  html: `
    <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
      <h2 style="color: #333;">Your Monthly Report</h2>
      <p>Hi there,</p>
      <p>Your report for <strong>January 2026</strong> is ready. Here's a quick snapshot:</p>
      <table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
        <tr style="background-color: #f4f4f4;">
          <td style="padding: 10px; border: 1px solid #ddd;">Emails Sent</td>
          <td style="padding: 10px; border: 1px solid #ddd;">12,430</td>
        </tr>
        <tr>
          <td style="padding: 10px; border: 1px solid #ddd;">Open Rate</td>
          <td style="padding: 10px; border: 1px solid #ddd;">34.2%</td>
        </tr>
        <tr style="background-color: #f4f4f4;">
          <td style="padding: 10px; border: 1px solid #ddd;">Click Rate</td>
          <td style="padding: 10px; border: 1px solid #ddd;">8.7%</td>
        </tr>
      </table>
      <a href="https://yourdomain.com/reports"
        style="display: inline-block; padding: 12px 24px; background-color: #007bff;
                color: #fff; text-decoration: none; border-radius: 4px;">
        View Full Report
      </a>
      <p style="color: #999; font-size: 12px; margin-top: 24px;">
        You're receiving this because you're subscribed to monthly reports.
      </p>
    </div>
  `,
};

A couple of things to keep in mind when working with HTML emails:

  • Always include a text fallback: Some email clients strip HTML or display plain text by default. The text field serves as a safety net. You will see the HTML version in clients that support it or plain text otherwise.
  • Inline your CSS: Most email clients ignore <style> blocks and external stylesheets. Gmail, for instance, strips <style> tags entirely. Use inline style attributes on each element for consistent rendering.
  • Stick to table-based layouts for complex structures: Email clients don't render CSS flexbox or grid reliably. For anything beyond simple stacked content, HTML tables are still the safest bet. It's not glamorous, but it works everywhere.
  • Test across clients: An email that looks great in Gmail might break in Outlook or Yahoo. Services like Litmus or Email on Acid let you preview your HTML across dozens of clients before sending.

When you run the code above, you should see the HTML email in your specified recipient email inbox, as shown in the image below:

Send HTML Emails with JavaScript

Send File Attachments via Email with JavaScript

Nodemailer handles attachments through the attachments array in your message options. Each attachment is an object that specifies the file name and its source, which could be a file path, a URL, a buffer, or even a raw string.

Here's an example with a local file attachment:

const mailOptions = {
  from: '"Your App" <noreply@yourdomain.com>',
  to: "recipient@example.com",
  subject: "Your Invoice - January 2026",
  text: "Please find your invoice attached.",
  html: "<p>Hi, please find your invoice for January 2026 attached to this email.</p>",
  attachments: [
    {
      filename: "invoice-jan-2026.pdf",
      path: "./invoices/invoice-jan-2026.pdf",
    },
  ],
};

This is what the email with attachments looks like:

Send File Attachments via Email with JavaScript

You can attach multiple files at once:

attachments: [
  {
    filename: "invoice.pdf",
    path: "./invoices/invoice-jan-2026.pdf",
  },
  {
    filename: "terms.pdf",
    path: "./docs/terms-of-service.pdf",
  },
  {
    // You can also attach from a URL
    filename: "logo.png",
    path: "https://yourdomain.com/assets/logo.png",
  },
],

If you want to embed an image directly in the HTML body (so it renders inline instead of as a download), use the cid (Content-ID) property:

const mailOptions = {
  from: '"Your App" <noreply@yourdomain.com>',
  to: "recipient@example.com",
  subject: "Welcome!",
  html: '<p>Welcome aboard!</p><img src="cid:company-logo" alt="Logo" />',
  attachments: [
    {
      filename: "logo.png",
      path: "./assets/logo.png",
      cid: "company-logo", // Referenced in the HTML src attribute
    },
  ],
};

Keep an eye on file sizes. Most email providers cap total message size at 10–25 MB. If your attachments are large, consider hosting the file and include a download link in the email body instead.

Send Emails to Multiple Recipients (To, CC, BCC)

Sending to multiple people is also straightforward. You can pass a comma-separated string or an array of addresses for the to, cc, and bcc fields.

const mailOptions = {
  from: '"Your App" <noreply@yourdomain.com>',
  to: "alice@example.com, bob@example.com",
  cc: "manager@example.com",
  bcc: "audit-log@yourdomain.com",
  subject: "Project Update - Q1 2026",
  text: "Here's the latest update on our Q1 progress.",
};

Send Emails Using an Email API with JavaScript

SMTP works well, but it isn't always the best fit for production. Email API gives you a cleaner integration, faster delivery, and less infrastructure to manage.

An Email API is an HTTP-based interface. Instead of opening an SMTP connection and managing everything yourself, you send an HTTP POST request with a JSON payload. The provider handles SMTP delivery, retry logic, bounce processing, and analytics on their end.

We'll use UniOne's Web API for our example. UniOne provides a RESTful API for sending transactional and marketing emails, with built-in support for HTML templates, variable substitutions, attachments, link tracking, and real-time webhooks. It also offers a 4-month free trial with 6,000 emails per month, which is plenty for testing and early-stage projects.

To get started, navigate to your terminal and run the command:

npm install node-fetch dotenv

We're using node-fetch to make HTTP requests and dotenv to keep the API key out of the source code. Create a .env file in the project root with your UniOne API key:

UNIONE_API_KEY=your-api-key-here

Next, create a .gitignore file and add the .env file.

Then, create a file called sendEmail.mjs (the .mjs extension enables ES module imports) and add the following:

import fetch from "node-fetch";
import "dotenv/config";

const BASE_URL = "https://api.unione.io/en/transactional/api/v1";

const headers = {
  "Content-Type": "application/json",
  "X-API-KEY": process.env.UNIONE_API_KEY,
};

const requestBody = {
  message: {
    recipients: [
      {
        email: "recipient@example.com",
        substitutions: {
          to_name: "Jane Doe",
        },
      },
    ],
    subject: "Welcome to Our Platform",
    from_email: "noreply@yourdomain.com",
    from_name: "Chisom Uma",
    body: {
      html: "<h1>Hello, {{to_name}}!</h1><p>Thanks for signing up.</p>",
      plaintext: "Hello, {{to_name}}! Thanks for signing up.",
    },
  },
};

async function sendEmail() {
  try {
    const response = await fetch(`${BASE_URL}/email/send.json`, {
      method: "POST",
      headers: headers,
      body: JSON.stringify(requestBody),
    });

    const data = await response.json();

    if (data.status === "success") {
      console.log("Email sent successfully. Job ID:", data.job_id);
    } else {
      console.error("Failed to send:", data);
    }
  } catch (error) {
    console.error("Request error:", error.message);
  }
}

sendEmail();

A few things to note about this code:

  • X-API-KEY header: UniOne accepts authentication either through this header or through an api_key field in the JSON body. The header approach is cleaner and keeps auth separate from your payload.
  • substitutions: UniOne has a built-in template engine. The {{to_name}} placeholder in the HTML body gets replaced with the value you pass in each recipient's substitutions object. This lets you personalize emails per recipient without building separate messages.
  • From_email: This must be an address on the domain you've verified in UniOne. If you verified yourdomain.com, the sender address needs to end in @yourdomain.com. For initial testing, you can use the free, pre-verified domain provided on your dashboard.
  • body.html and body.plaintext: Always include both. Some email clients only render plain text, and having a plaintext fallback also helps with spam scoring.

Now, run the code using the node sendEmail.mjs command. You should receive an email in your recipient email inbox as shown below:

Email with Attachments using JavaScript

Send to Multiple Recipients with Personalization

One of the strengths of UniOne's API is that you can send to multiple recipients in a single API call, with unique substitutions for each person:

const requestBody = {
  message: {
    recipients: [
      {
        email: "alice@example.com",
        substitutions: { to_name: "Alice", plan: "Pro" },
      },
      {
        email: "bob@example.com",
        substitutions: { to_name: "Bob", plan: "Starter" },
      },
      {
        email: "carol@example.com",
        substitutions: { to_name: "Carol", plan: "Enterprise" },
      },
    ],
    subject: "Your {{plan}} Plan Is Active",
    from_email: "noreply@yourdomain.com",
    from_name: "Your App",
    body: {
      html: `
        <p>Hi {{to_name}},</p>
        <p>Your <strong>{{plan}}</strong> plan is now active.
        You're all set to get started.</p>
      `,
      plaintext: "Hi {{to_name}}, your {{plan}} plan is now active.",
    },
  },
};

Each recipient gets a personalized email. Alice sees "Your Pro Plan Is Active," while Bob sees "Your Starter Plan Is Active". The substitution happens server-side at UniOne, so you don't need to loop through recipients and make separate API calls.

Send Emails with Attachments

UniOne supports attachments up to approximately 7 MB (10 MB total encoded message size). Attachments need to be Base64-encoded. Here's how to attach a file:

import { readFileSync } from "fs";

const fileContent = readFileSync("./invoices/invoice-jan-2026.pdf");
const base64Content = fileContent.toString("base64");

const requestBody = {
  message: {
    recipients: [{ email: "recipient@example.com" }],
    subject: "Your Invoice - January 2026",
    from_email: "billing@yourdomain.com",
    from_name: "Billing Team",
    body: {
      html: "<p>Hi, your invoice for January 2026 is attached.</p>",
      plaintext: "Hi, your invoice for January 2026 is attached.",
    },
    attachments: [
      {
        type: "application/pdf",
        name: "invoice-jan-2026.pdf",
        content: base64Content,
      },
    ],
  },
};

When you run the code, you should see an email like this:

Send Emails Using an Email API with JavaScript

You can also embed images inline using inline_attachments with a name that serves as the Content-ID, then reference it in your HTML with <img src="cid:Content-ID">.

Enable Link and Read Tracking

UniOne can track whether recipients open your email and click links inside it. Enable this by adding track_links and track_read to your message object:

const requestBody = {
  message: {
    recipients: [{ email: "recipient@example.com" }],
    subject: "Check Out Our New Features",
    from_email: "noreply@yourdomain.com",
    from_name: "Your App",
    body: {
      html: '<p>We just shipped something new. <a href="https://yourdomain.com/features">Take a look</a>.</p>',
      plaintext: "We just shipped something new. Visit https://yourdomain.com/features",
    },
    track_links: 1,
    track_read: 1,
  },
};

Once enabled, you can view open rates and click-through data in the UniOne dashboard, or set up webhooks to receive real-time event notifications.

Common Errors When Sending Emails with JavaScript

Even with a solid setup, things may still go wrong. Email sending involves multiple systems: your application, the SMTP server, DNS records, the recipient's mailbox provider, and a failure at any point in the chain can kill delivery. Here are the errors developers run into most often, along with what actually causes them and how to fix them.

Authentication failures (535 error)

This is the most common one. You see 535 Authentication failed or Invalid login in your logs. The usual culprits are:

  • Wrong username or password. Double-check your credentials, especially if you copy-pasted them. Trailing spaces and invisible characters cause more failed logins than you'd think.
  • Your email provider requires an app-specific password. Gmail, for instance, won't let you authenticate with your regular account password if you have two-factor authentication enabled. You need to generate an app password in your Google account settings.
  • The provider has disabled "less secure app" access. Some providers require OAuth2 authentication instead of plain username/password. Nodemailer supports OAuth2 natively. Check the Nodemailer documentation for setup instructions.

Connection timeouts

Your code hangs and eventually throws ETIMEDOUT or ECONNREFUSED. This typically means:

  • A firewall is blocking the SMTP port. Cloud providers like AWS, GCP, and Azure block port 25 by default. Try port 587 or 465 instead.
  • Your hosting provider restricts outbound SMTP. Some platforms (Vercel, Netlify, certain shared hosts) don't allow direct SMTP connections at all. In that case, use an Email API instead.
  • The SMTP server address is wrong. Verify the hostname with your provider's documentation.

TLS/SSL handshake errors

Messages like “unable to verify the first certificate or self-signed certificate in certificate chain” point to TLS configuration issues. As a temporary fix during development, you can set tls: { rejectUnauthorized: false } in your Nodemailer transporter. But don't ship that to production — it disables certificate validation entirely, which opens the door to man-in-the-middle attacks.

The real fix is to make sure your Node.js version is up to date (some older versions don't support newer certificate chains) and that your SMTP provider's certificates are valid.

Emails landing in spam

Technically not a delivery error, but it's the outcome developers complain about most. Your code works, the email sends, and then it vanishes into the recipient's junk folder. The causes are usually related to your email sending reputation, which is a combination of your domain reputation, sending IP reputation, and email authentication setup for your domain.

Common reasons emails get flagged as spam:

  • Missing or misconfigured SPF, DKIM, or DMARC records on your sending domain’s DNS.
  • Sending from a brand-new domain or IP address that hasn't been properly warmed up.
  • High bounce rates from sending to invalid addresses.
  • Spammy subject lines or content (all caps, excessive exclamation marks, misleading claims, etc.)
  • No unsubscribe link in marketing emails.

We'll cover deliverability in more detail in a later section.

Rate limiting and throttling

Every email provider imposes sending limits. If you blast out too many emails all at once, expect your messages to get queued, delayed, or rejected. Check your provider's rate limits and implement sending queues with appropriate delays between batches. Note that mailbox providers also dislike sudden volume spikes, which increases the chance of spam blocking.

Providers like UniOne will do the throttling for you automatically, but you still need to take it into account when planning your campaigns.

Invalid recipient addresses

Sending to addresses that don't exist generates hard bounces. Too many hard bounces will damage your sender's reputation quickly. Validate email addresses before sending. At minimum, check the format. For production systems, use an email validation API to verify that the mailbox actually exists.

Testing Email Sending Before Production

Sending test emails to real addresses during development is a bad idea. You'll spam real inboxes, burn through your provider's sending limits, and risk accidentally emailing customers with test data.

Here are better approaches:

Use Ethereal for throwaway test accounts

Ethereal (ethereal.email) is a fake SMTP service built by the Nodemailer team. It captures emails without delivering them and gives you a preview URL to inspect each message in your browser. Nodemailer has built-in support for generating Ethereal test accounts:

const nodemailer = require("nodemailer");

async function sendTestEmail() {
  // Generate a test account on the fly
  const testAccount = await nodemailer.createTestAccount();

  const transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,
    secure: false,
    auth: {
      user: testAccount.user,
      pass: testAccount.pass,
    },
  });

  const info = await transporter.sendMail({
    from: '"Test Sender" <test@example.com>',
    to: "recipient@example.com",
    subject: "Test Email",
    html: "<p>This is a <strong>test email</strong> for development.</p>",
  });

  console.log("Preview URL:", nodemailer.getTestMessageUrl(info));
}

sendTestEmail();

The preview URL logs directly to your console. Click it, and you'll see exactly how the email renders: HTML, headers, attachments, everything, without actually sending a single real message.

Test SMTP connections with debugging tools

If you're troubleshooting connection issues, tools like UniOne's Email Testing service will let you run SMTP debug tests directly from your browser. You get a full session log showing each step of the SMTP handshake, which makes it much easier to pinpoint configuration errors, authentication failures, or TLS issues.

How to Improve Email Deliverability and Avoid Spam

Even with perfect code, your emails can still end up in spam. Deliverability is a separate discipline from email sending. It's about convincing mailbox providers (such as Gmail, Outlook, Yahoo, etc.) that your messages are legitimate and wanted, thereby forming a positive email sending reputation

Here are some ways you can make this work:

Set up SPF, DKIM, and DMARC

These three DNS-based authentication protocols are non-negotiable for production email sending.

  • SPF (Sender Policy Framework) tells receiving servers which IP addresses are authorized to send email on behalf of your domain.
  • DKIM (DomainKeys Identified Mail) attaches a cryptographic signature to each email so the receiver can verify it hasn't been tampered with.
  • DMARC (Domain-based Message Authentication, Reporting, and Conformance) ties SPF and DKIM together and tells receivers what to do with emails that fail authentication checks.

If you're using an email service provider, they'll automatically generate the DNS records you need to add. Without these records, major mailbox providers will treat your emails with much suspicion.

Checkout our guide to implement SPF, DKIM, and DMARC.

Warm up new domains and IPs

If you start sending thousands of emails from a brand-new domain or IP address, mailbox providers will flag you immediately. Start with low volumes (a few hundred per day) and gradually increase over 3–4 weeks. This builds a positive sending history.

Monitor bounce rates

Hard bounces (sending to nonexistent addresses) hurt your reputation fast. Clean your recipient lists regularly and validate email addresses before adding them to your database.

Write content that doesn't trigger spam filters

Avoid all-caps subject lines, excessive punctuation, misleading "Re:" or "Fwd:" prefixes, and image-only emails with no text content. Keep your text-to-image ratio balanced.

Include an unsubscribe mechanism

For marketing and promotional emails, this isn't optional, as it's required by law in most U.S jurisdictions. Even for transactional emails, giving recipients a way to manage their preferences reduces spam complaints.

For a deeper dive into these topics, check out these resources on how to avoid email going to spam and how to Improve Email Deliverability.

Sending Limits and Regulations You Should Respect

Before you start sending, there are practical limits and legal requirements you need to be aware of.

Provider-imposed limits

Every email service has sending caps. Free tiers typically allow anywhere from a few hundred to a few thousand emails per month. Paid plans scale higher, but even enterprise accounts have per-minute or per-hour rate limits to prevent abuse and protect shared infrastructure. Exceeding these limits can result in queued messages, temporary blocks, or account suspension. Always check your provider's documentation and build rate limiting into your sending logic.

CAN-SPAM Act (United States)

If you're sending commercial emails to recipients in the U.S., you must include a valid physical postal address, a clear unsubscribe mechanism, and accurate "From" and subject lines. Violating CAN-SPAM can result in penalties of up to $51,744 per email.

GDPR (European Union)

If your recipients are in the EU, you need explicit consent before sending marketing emails. You also need to provide easy opt-out options and handle personal data (including email addresses) according to GDPR's data protection requirements.

CASL (Canada)

Canada's Anti-Spam Legislation requires express consent for commercial electronic messages, with specific requirements for identification and unsubscribe mechanisms.

Conclusions

Sending email from JavaScript is straightforward once you understand how everything works, which is why I wrote this tutorial. The approach you choose depends entirely on what you're building.

For quick prototypes or simple "contact us" links, client-side methods like mailto: get the job done with zero setup. EmailJS can do the job for personal projects where security isn't a primary concern, but its exposed credentials make it a poor choice for anything facing critical tasks.

For production applications, server-side sending is the clear path. Nodemailer gives you full control over SMTP connections and works with any provider. It's a solid choice when you want flexibility and don't mind managing the transport layer yourself. Email APIs take it a step further by handling SMTP connections, deliverability, retries, and analytics for you, letting you focus on your application logic instead of email infrastructure.

Regardless of which method you use, the fundamentals stay the same: keep your data safe, authenticate your sending domain, validate recipient addresses, and test thoroughly before deploying to production.

Email is one of those features that seems easy until it isn't. Get the basics right, and it'll serve your application reliably for years.

If you're looking for a production-ready email infrastructure to pair with your JavaScript application, UniOne offers a set of tools built for developers:

  • Email API: UniOne provides a RESTful API for sending transactional and marketing emails programmatically. It supports HTML templates, attachments, merge tags, real-time webhooks, and detailed delivery analytics. Integration takes minutes, and the API handles SMTP, retry logic, and delivery optimization on the backend. UniOne processes millions of requests per hour and provides a generous 4-month free trial with 6,000 emails per month to get started.
  • SMTP Service: If your application or framework already uses SMTP, UniOne's SMTP service works as a drop-in relay. Configure your host, port, and user credentials, and you're good to go. It supports up to 150,000 hourly emails per connection, with features like variable substitution, click tracking, and unsubscribe link management accessible through custom headers.
  • Email Testing: With UniOne, you get a free SMTP debug tool that logs the full communication between your application and the mail server. It's useful for diagnosing connection errors, authentication failures, and TLS issues before you start sending to real recipients.

FAQ

Can I send email from JavaScript without a backend server?

Technically, yes, using mailto: or client-side libraries like SMTP.js. But these approaches come with serious limitations. The former just opens the user's email client, and the latter exposes your credentials in the browser. For reliable, secure email sending, you need backend code (Node.js, Express, etc.) that handles the SMTP connections or API calls.

What is the best way to send email using JavaScript in production?

Use a server-side approach, either Nodemailer with SMTP or an Email API provider. Both keep your credentials secure and give you control over delivery, error handling, and logging. Email APIs are generally easier to scale and come with built-in features like analytics and bounce management.

Is Nodemailer free to use?

Yes. Nodemailer is open-source and licensed under MIT No Attribution. It's free for personal and commercial use with no restrictions. You will, however, need to pay for an email provider’s plan if you don’t run an in-house SMTP solution, and the costs depend on your sending volume.

Related Articles
Blog
For beginners
Email Marketing for Jewelry Brand: Strategies, Tips, and Real Campaign Examples
Your go-to guide to jewelry email marketing
Valeriia Klymenko
11 march 2026, 06:2715 min
Blog
For beginners
5 Best Email APIs for Developers
Learn about the key features to look for in the Email API to choose the best one and the key service
Alexey Kachalov
13 february 2025, 12:1810 min
Blog
For beginners
Email Marketing for Mobile Apps: A Complete Guide to Driving Growth and Retention
Email marketing as a hidden gem for apps to drive sales and retention
Valeriia Klymenko
15 october 2025, 12:3815 min