Skip to content
NewHost
Menu

Send Email From a Node.js App - SMTP, Nodemailer and SPF

How to send email from a Node.js or Next.js app - SMTP with Nodemailer, contact forms that reach the inbox, SPF and DMARC alignment, retries and POPIA basics.

By NewHost team · · 6 min read

The simplest reliable way to send email from a Node.js app is to connect to an authenticated SMTP server with Nodemailer, using a real mailbox on your own domain as the sender. That covers contact forms, password resets and order confirmations for most small and medium apps. When you need high volumes, detailed delivery analytics or marketing campaigns, a dedicated transactional email service is the better fit. Either way, what decides whether your mail lands in the inbox is not the code: it is authentication, alignment and sensible sending habits.

Your options

Option How it works Good for
SMTP through a mailbox on your domain Your app logs in to your mail server like an email client and sends Contact forms, notifications, low to moderate volume
Transactional email service Your app calls the provider's API or SMTP relay High volume, delivery webhooks, bounce handling
The server's local mail function The web server hands mail to a local mail program Avoid it: it is often unauthenticated and ends up in spam

For most small businesses and agencies, the first option is the right start. You already have the mailbox, mail is signed for your domain and there is nothing new to pay for.

Sending with Nodemailer over SMTP

Install the library:

npm install nodemailer

Keep the connection details in environment variables, never in your code or repository:

SMTP_HOST=mail.example.co.za
SMTP_PORT=465
[email protected]
SMTP_PASS=the-mailbox-password

Then create one transporter and reuse it:

import nodemailer from "nodemailer";

const port = Number(process.env.SMTP_PORT ?? 465);

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port,
  secure: port === 465, // true for SSL on 465, false for STARTTLS on 587
  auth: {
    user: process.env.SMTP_USER, // the full mailbox address
    pass: process.env.SMTP_PASS,
  },
});

export async function sendContactEmail({ name, email, message }) {
  const info = await transporter.sendMail({
    from: '"Example Website" <[email protected]>',
    to: "[email protected]",
    replyTo: email,
    subject: `Website enquiry from ${name}`,
    text: `From: ${name} <${email}>\n\n${message}`,
  });
  return info.messageId;
}

To check the settings when the app starts, call await transporter.verify() once and log a clear error if it fails. The Nodemailer documentation lists every other transport option.

On NewHost email hosting, the SMTP server for a mailbox is mail. followed by your domain, the username is the full email address, and the password is the mailbox's own password, which you can reset from the mailbox page in the dashboard. Create a dedicated mailbox such as website@ for the app, so you can change its password without affecting a person's inbox. If you haven't set up mailboxes yet, see how to set up email on your own domain.

The contact form mistake almost everyone makes

It is tempting to put the visitor's address in from, so that you can hit Reply. Don't. Your server is not allowed to send mail as [email protected], and receiving servers will reject it or bin it because it fails SPF, DKIM and DMARC for that domain.

Instead:

  • from is always your own address, the one your app logs in with (or an alias of it).
  • replyTo is the visitor's address, so Reply still goes to them.
  • subject says what it is, so the message is easy to filter.

That's what the example above does.

Protect the form

An unprotected contact form gets found by bots and used to send junk, which hurts your domain's reputation. Add at least one of: a CAPTCHA, a hidden honeypot field that real users never fill in, and a rate limit per IP address. Validate the email address format and cap the message length before you send anything.

Make sure your mail authenticates

Receiving servers check three things before trusting mail from your domain:

  • SPF lists the servers allowed to send for your domain.
  • DKIM adds a cryptographic signature that proves the message wasn't altered.
  • DMARC checks that SPF or DKIM passes for the same domain as the From address and tells receivers what to do if not.

When you send through your own mailbox's SMTP server, the mail leaves from the server your SPF record already allows. If you switch to a transactional service later, you must add that service to SPF and set up its DKIM records, or your mail will start failing DMARC. Our guide to SPF, DKIM and DMARC has example records.

To check a real message, send one to a Gmail address, open it, choose Show original and look for SPF: PASS, DKIM: PASS and DMARC: PASS. If one fails, why your emails go to spam walks through the usual causes.

Don't make users wait for SMTP

An SMTP conversation can take a second or two, and occasionally the mail server is slow or briefly unavailable. Two habits keep your app responsive and your mail reliable:

  1. Send after responding where you can. For a contact form, store the enquiry first, respond to the user, and send the notification from a background job or queue.
  2. Retry with a limit. If sending fails, retry a few times with a growing delay, then record the failure where someone will see it. Storing the enquiry first means nothing is lost if every retry fails.

A scheduled task that sends anything still pending works well for this. See scheduled tasks and cron jobs for Node.js.

Logs, personal information and POPIA

Email content often contains personal information: names, phone numbers, sometimes ID numbers or medical details someone typed into a form. Log the message ID, the time and whether it was sent, not the full body. Keep stored enquiries only as long as you need them, and restrict who can see them. Our POPIA guide for developers covers the security safeguards the Act expects.

Marketing email is different

A password reset or order confirmation is transactional: the user asked for it. A newsletter is direct marketing. POPIA section 69 sets rules for direct marketing by electronic communication, including consent and an easy way to opt out. Send marketing through a service built for it, with proper unsubscribe handling, not through the SMTP mailbox your app uses for transactional mail. For your specific situation, take professional advice.

Frequently asked questions

Port 465 or 587?

Both are fine. Port 465 uses SSL from the start (secure: true in Nodemailer); port 587 starts in plain text and upgrades with STARTTLS (secure: false). Use whichever your mail provider documents.

Can I send from an address that isn't a mailbox?

Most mail servers only let an authenticated user send as their own address or one of its aliases. If you want mail to come from noreply@, create it as an alias of the app's mailbox, or as its own mailbox.

How many emails can I send through a normal mailbox?

Mailboxes are meant for normal business use, and mail servers limit unusual volumes to protect everyone's reputation. If your app sends thousands of messages a day, move to a transactional email service.

Why do my app's emails reach Gmail but not Outlook (or the other way round)?

Each provider weighs signals differently. Check that SPF, DKIM and DMARC all pass, that the From domain matches your sending domain and that messages include a plain-text part. New domains also need time to build a sending reputation.

Need mailboxes for your app and your team? NewHost email hosting gives you SMTP, IMAP and webmail on your own domain, and the DNS records are managed in the same dashboard as your Node.js hosting.

Related guides

Ready to launch on NewHost?

Choose a plan and go live today, or tell us what you need and we'll recommend the right setup.