Send email with SMTP from a Node.js application when you need a standards-based delivery path that works with existing libraries, frameworks, and operational tooling. This guide uses Nodemailer, environment variables, and the SMTP credentials provisioned for your Volanea account to send one transactional email safely.
SMTP is a useful option when your application, framework, or legacy system already knows how to speak Simple Mail Transfer Protocol. Rather than adopting a provider-specific client library, you configure a standard SMTP transport and let your application submit a complete email message to Volanea for delivery.
This page shows a complete Node.js example using Nodemailer. It installs the required dependency, reads the SMTP API key from an environment variable, validates the configuration before sending, and sends a plain-text and HTML transactional email in one call.
What you need before you send email with SMTP
Before running the example, make sure you have the following:
- A Volanea account with SMTP sending credentials available for your account.
- A verified sending domain and an approved sender address on that domain.
- The SMTP hostname, port, security mode, and username provided for your Volanea SMTP configuration.
- An SMTP API key or password stored locally as an environment variable.
- Node.js installed on your development machine or deployment environment.
- A recipient inbox you control for testing.
Do not guess SMTP connection values. In particular, the hostname, port, username, and whether the connection expects implicit TLS or STARTTLS must match the SMTP settings issued for your Volanea account. Put those values in environment variables so that the application code stays portable between local development, staging, and production.
SMTP credentials are secrets. Treat an SMTP API key like a password that can send email on behalf of your application. Never commit it to Git, paste it into a browser-side bundle, include it in a support ticket, or expose it through a client-side environment variable convention such as NEXT_PUBLIC_*.
Why use SMTP instead of a REST endpoint?
SMTP is an established protocol that most programming languages, web frameworks, CRMs, queue workers, and server-side platforms already support. If you are moving an existing application from another email provider, an SMTP transport can often reduce the migration to a configuration change plus credential rotation.
For a new service, an HTTP API can provide a more structured request and response model. Volanea also provides a REST send endpoint, template support, contact operations, suppression handling, and batch sending in its email API reference and setup guides. But SMTP is a practical choice when interoperability, framework compatibility, or an existing mail abstraction matters more than provider-specific request features.
The email itself is still an RFC-style message: it has an envelope sender, one or more recipients, headers, a subject, and one or more MIME body parts. Nodemailer builds that message for you, correctly encoding headers and generating the multipart structure required when you supply both text and html content.
Install Nodemailer and environment-variable support
This guide uses Node.js with ECMAScript modules and Nodemailer. Nodemailer includes SMTP transport support, so you do not need a Volanea-specific SDK to submit mail through SMTP.
Run this exact command from your Node.js project directory:
npm install nodemailer dotenv
If you are starting from an empty directory, initialize a package first:
mkdir volanea-smtp-example
cd volanea-smtp-example
npm init -y
npm install nodemailer dotenv
Update package.json so Node treats .js files as ECMAScript modules:
{
"name": "volanea-smtp-example",
"private": true,
"type": "module",
"scripts": {
"send": "node send-email.js"
}
}
The dotenv package is used only to load local development values from a .env file. In production, set the same variables through your host, container platform, CI/CD secret store, or operating system environment. Your application code should not need to change between environments.
Configure Volanea SMTP credentials with environment variables
Create a file named .env in the project root. Replace every placeholder with the SMTP values provisioned for your Volanea account and a sender address from your verified domain.
# Use the SMTP hostname supplied for your Volanea account.
VOLANEA_SMTP_HOST=your-volanea-smtp-host
# Use the exact port supplied for the selected SMTP security mode.
VOLANEA_SMTP_PORT=587
# true for an implicit TLS connection; false for STARTTLS/plain connection upgrade.
VOLANEA_SMTP_SECURE=false
# Use the SMTP username supplied for your Volanea account.
VOLANEA_SMTP_USERNAME=your-smtp-username
# Store the SMTP API key/password here locally. Never commit this file.
VOLANEA_SMTP_API_KEY=your-smtp-api-key
# This address must be permitted for your configured sending domain.
EMAIL_FROM="Example App <notifications@your-verified-domain.com>"
# Use an inbox you control while testing.
EMAIL_TO=you@example.com
Add the .env file to .gitignore immediately:
node_modules/
.env
The name VOLANEA_SMTP_API_KEY is intentionally explicit: it makes it clear that the value is a credential, not a public application setting. The SMTP client passes this value as the password portion of SMTP authentication. If your Volanea SMTP configuration uses a separately generated SMTP password rather than a general API key, store that issued secret in the same environment variable instead.
Choosing the correct secure value
In Nodemailer, secure: true means the client starts the connection inside TLS, commonly called implicit TLS. secure: false means Nodemailer starts a normal SMTP connection and can upgrade it using STARTTLS when the server supports or requires it.
Do not infer the security mode from the port alone unless your Volanea-issued SMTP configuration explicitly says to do so. A mismatch is a common cause of TLS handshake errors and connection resets. The sample reads VOLANEA_SMTP_SECURE as a literal true or false string so that security behavior remains visible in configuration rather than hidden in application logic.
For local testing, avoid disabling certificate validation. Configuration such as tls: { rejectUnauthorized: false } may conceal a DNS, certificate, proxy, or interception problem and should not be used as a production workaround.
Complete Node.js SMTP sending example
Create a file named send-email.js in the same directory as .env.
import "dotenv/config";
import nodemailer from "nodemailer";
function requiredEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
function parseBoolean(value, name) {
if (value === "true") return true;
if (value === "false") return false;
throw new Error(`${name} must be exactly "true" or "false".`);
}
const smtpPort = Number(requiredEnv("VOLANEA_SMTP_PORT"));
if (!Number.isInteger(smtpPort) || smtpPort < 1 || smtpPort > 65535) {
throw new Error("VOLANEA_SMTP_PORT must be a valid TCP port number.");
}
const transporter = nodemailer.createTransport({
host: requiredEnv("VOLANEA_SMTP_HOST"),
port: smtpPort,
secure: parseBoolean(
requiredEnv("VOLANEA_SMTP_SECURE"),
"VOLANEA_SMTP_SECURE",
),
auth: {
user: requiredEnv("VOLANEA_SMTP_USERNAME"),
pass: requiredEnv("VOLANEA_SMTP_API_KEY"),
},
});
async function main() {
// Tests DNS resolution, TCP/TLS negotiation, and SMTP authentication.
await transporter.verify();
const info = await transporter.sendMail({
from: requiredEnv("EMAIL_FROM"),
to: requiredEnv("EMAIL_TO"),
subject: "Your Volanea SMTP integration is working",
text: [
"Hello,",
"",
"This transactional email was sent from Node.js through SMTP.",
"",
"If you received it, the SMTP integration is working.",
].join("\n"),
html: `
<!doctype html>
<html lang="en">
<body>
<h1>Your Volanea SMTP integration is working</h1>
<p>Hello,</p>
<p>This transactional email was sent from <strong>Node.js</strong> through SMTP.</p>
<p>If you received it, the SMTP integration is working.</p>
</body>
</html>
`,
});
console.log("Email accepted by SMTP server");
console.log("Message ID:", info.messageId);
console.log("Accepted recipients:", info.accepted);
console.log("Rejected recipients:", info.rejected);
}
main().catch((error) => {
console.error("Unable to send email");
console.error(error);
process.exitCode = 1;
});
Run the example:
npm run send
A successful run means the SMTP server accepted the message for processing. It does not, by itself, guarantee inbox placement or final delivery to the recipient. A message can subsequently be deferred, bounced, filtered, or rejected by the receiving system. Check the recipient inbox and the message activity available in your Volanea account when validating a production integration.
What the code does
The requiredEnv() helper fails early with a useful error if a secret or SMTP setting is missing. This is better than allowing a default hostname, an empty password, or an undefined sender address to create a confusing SMTP error later.
nodemailer.createTransport() creates a reusable SMTP transport. In a long-running application, create this transport once during application startup and reuse it for multiple sends. Recreating a transport for every request adds unnecessary DNS lookups, TCP connections, TLS negotiation, and authentication work.
transporter.verify() checks that the application can reach the configured SMTP server and authenticate. Keeping it in this standalone script is useful because the error appears before a real message is submitted. In a web application, run a similar connectivity check during deployment validation or health diagnostics rather than before every single email send.
transporter.sendMail() is asynchronous and returns a Promise. The await keyword is essential: it waits for Nodemailer to submit the message and exposes send errors to the surrounding try/catch flow. The info object can include a generated message ID plus accepted and rejected recipient arrays, which are useful for application logs.
The example sends both text and html. This is a recommended baseline for transactional messages. The HTML version gives capable email clients a formatted experience, while the text version provides a readable fallback for plain-text clients, accessibility tools, security-focused mail readers, and recipients whose HTML rendering is disabled.
Use a verified sender and recipient while testing
The from value is not just a display label. It represents the identity that recipients see and the domain whose authentication and sending reputation are evaluated by mailbox providers. Use a sender address that belongs to a domain you have authenticated and configured for sending.
A good development pattern is to use a dedicated sender identity such as notifications@your-verified-domain.com or test@your-verified-domain.com. Keep transactional mail separate from personal mailboxes and avoid changing the visible sender address casually. Recipient trust, reply handling, domain alignment, and support workflows are easier to manage when a sender identity has a clear purpose.
Start by sending to an inbox you own. Confirm these items:
- The message arrives at the intended address.
- The sender name and address render as expected.
- The subject is correct and not accidentally prefixed by a test label.
- The plain-text fallback is readable.
- The HTML layout is usable on desktop and mobile clients.
- Links point to the correct environment and use HTTPS.
- Reply handling works if recipients may reply to the message.
Do not test a new integration by sending a large batch of real customer messages. A small controlled test lets you catch a wrong sender, malformed template, incorrect recipient mapping, or staging URL before the issue becomes customer-facing.
Before sending to a customer list, you can also reduce avoidable bounces by checking addresses with the email address verification tool. Verification is not a substitute for permission, suppression handling, or bounce processing, but it can help identify malformed or risky addresses before a send.
Add SMTP sending to an application service
A command-line script is useful for proving credentials, but production applications usually put email sending behind a small service module. That keeps transport setup, sender identity, logging, and error behavior consistent across password resets, receipts, alerts, and other transactional flows.
Here is a focused example service module. It uses the same environment variables as the complete script but lets the caller provide recipient and message content.
import "dotenv/config";
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
host: process.env.VOLANEA_SMTP_HOST,
port: Number(process.env.VOLANEA_SMTP_PORT),
secure: process.env.VOLANEA_SMTP_SECURE === "true",
auth: {
user: process.env.VOLANEA_SMTP_USERNAME,
pass: process.env.VOLANEA_SMTP_API_KEY,
},
});
export async function sendPasswordResetEmail({ to, resetUrl }) {
return transporter.sendMail({
from: process.env.EMAIL_FROM,
to,
subject: "Reset your password",
text: `Reset your password by opening this link: ${resetUrl}`,
html: `
<p>We received a request to reset your password.</p>
<p><a href="${resetUrl}">Reset your password</a></p>
<p>If you did not request a reset, you can ignore this email.</p>
`,
});
}
Call this function from server-side code only. A password reset endpoint should generate a short-lived, single-use reset token on the server, build a URL from a trusted application origin, persist enough information to validate the token, and then queue or send the email. Never generate privileged reset links in browser code, and never return the SMTP API key to a client.
Avoid sending directly inside a fragile request path
For low-volume transactional messages, sending inline can be acceptable when your application has suitable timeouts and retry behavior. But email delivery depends on an external network connection, so a synchronous send can increase user-facing request latency and create an ambiguous result if the app times out after the SMTP server accepts the message.
For important workflows, consider writing an email job to a durable queue or outbox table as part of the application transaction. A worker can send the message, record the SMTP result, retry transient failures with backoff, and preserve an audit trail. This architecture also makes it easier to avoid duplicate sends when an API request is retried.
Be deliberate about retrying. Network failures before SMTP acceptance may be retryable. But if your process loses the response after a server has accepted the message, an automatic retry can create a duplicate email. Store an application-level event ID, record send attempts, and use that record to decide whether a retry is safe.
Compose transactional messages correctly
Transactional email is operational communication triggered by a user action or an account event: an OTP, receipt, password reset, security alert, invitation, or subscription confirmation. The message should be specific, timely, and directly related to that event.
A practical transactional message has these characteristics:
- A clear sender identity that matches the product or account context.
- A subject line that explains the action without unnecessary urgency or ambiguity.
- A concise first sentence that tells the recipient why they received the message.
- One obvious primary action when an action is required.
- A plain-text fallback that contains the same essential information.
- Links that point to the correct production or test environment.
- A reply path or support instruction when users may need help.
Keep sensitive information out of email whenever possible. Email inboxes may be shared, forwarded, indexed by workplace systems, or accessed from compromised devices. A receipt can say that a payment was received without displaying more personal data than needed. A security alert can identify the account event and direct the recipient to a secure account page rather than embedding sensitive account details.
HTML and Content-Type considerations
When you provide html in Nodemailer, the library constructs the appropriate MIME part and Content-Type: text/html header. When you provide both text and html, Nodemailer creates a multipart alternative message so a receiving client can select the best representation.
Do not pass JSON to an SMTP transport and expect it to become an email. SMTP submits a fully formed message, not an HTTP JSON request. Similarly, do not label ordinary HTML as a plain-text body or manually set a conflicting Content-Type header. Wrong content types can cause raw markup to appear in the inbox, make attachments unreadable, or create inconsistent rendering across clients.
If you construct raw MIME messages yourself, ensure line endings, multipart boundaries, character encodings, and headers are correct. For most application integrations, use Nodemailer message fields instead. It reduces the chance of malformed MIME while retaining support for standard capabilities such as HTML, text alternatives, attachments, reply-to addresses, and custom headers.
Common errors when sending with Node.js SMTP
SMTP failures often contain a useful status code or server response. Log the error message, the operation that failed, and a safe correlation ID for the application event. Do not log the SMTP API key, the full Authorization-equivalent credential, reset tokens, or sensitive message bodies.
Authentication fails with EAUTH or an SMTP 535 response
An EAUTH error or a response indicating authentication failure usually means the SMTP username, SMTP API key/password, or authentication configuration is wrong.
Check the following:
- Confirm
VOLANEA_SMTP_USERNAMEmatches the exact SMTP username provisioned for the account. - Confirm
VOLANEA_SMTP_API_KEYcontains the active SMTP credential with no copied quotes, spaces, or line breaks. - Confirm
.envis loaded in local development and that your production platform has the variable configured. - Confirm you did not accidentally use a browser-safe or public environment variable for the secret.
- Confirm the key has not been revoked, rotated, or replaced.
- Confirm you are using the SMTP credentials, not unrelated dashboard, API, or DNS credentials.
A frequent issue is a key copied with a trailing newline. Delete and retype the value in the secret manager if necessary. Another is using a general REST API credential in a SMTP password field without checking whether the provisioned SMTP configuration accepts it.
Connection timeout, ECONNREFUSED, or DNS lookup failure
These errors happen before authentication. They commonly indicate an incorrect hostname, incorrect port, blocked outbound traffic, an unavailable network route, or a proxy/firewall policy.
Use the SMTP hostname and port issued for your Volanea account. Check that your cloud platform, corporate network, container policy, or hosting provider permits outbound traffic to that port. Some environments restrict outbound SMTP traffic, particularly on commonly used SMTP ports, to prevent abuse.
Do not “fix” a timeout by changing ports at random. Match the host, port, and security mode as a set. If the configuration is correct but your environment blocks egress, use the allowed SMTP submission configuration for that environment or consider the REST API path instead.
TLS errors or wrong version number
TLS failures usually mean the secure setting does not match the configured SMTP port and server security mode. For example, initiating implicit TLS on a connection that expects a plaintext SMTP greeting and STARTTLS can fail immediately; the reverse mismatch can fail too.
Set VOLANEA_SMTP_SECURE=true only when the supplied configuration calls for an immediate TLS connection. Set it to false when the supplied setup uses STARTTLS after connecting. Keep certificate validation enabled, because bypassing it can turn a configuration error into a security issue.
The code exits before the email is sent
This is often an async/await mistake. transporter.sendMail() returns a Promise, so a script or request handler must await it or return the Promise to the framework.
Incorrect:
transporter.sendMail(message);
console.log("Sent");
Correct:
const info = await transporter.sendMail(message);
console.log("Accepted:", info.accepted);
In an Express-style handler, use await inside an async function and route errors to your error middleware. In a queue worker, await the send before marking the job complete. If you fire and forget, a process shutdown, serverless freeze, unhandled rejection, or job acknowledgment can prevent reliable delivery tracking.
The email shows HTML tags instead of a rendered message
This is usually a body-format problem. Put HTML in the html field, not the text field. Provide a separate readable plain-text version in text.
Incorrect:
await transporter.sendMail({
from: process.env.EMAIL_FROM,
to: "recipient@example.com",
subject: "Welcome",
text: "<h1>Welcome</h1><p>Your account is ready.</p>",
});
Correct:
await transporter.sendMail({
from: process.env.EMAIL_FROM,
to: "recipient@example.com",
subject: "Welcome",
text: "Welcome. Your account is ready.",
html: "<h1>Welcome</h1><p>Your account is ready.</p>",
});
This is the practical version of a wrong Content-Type problem. A plain-text MIME part will display HTML tags literally; an HTML MIME part will allow an email client to render the permitted markup.
The SMTP server accepts the message, but it does not arrive
SMTP acceptance means your application successfully handed the message to the sending service. It does not guarantee that the recipient mailbox placed it in the primary inbox or delivered it immediately.
First, check spam, promotions, quarantine, and any filtering rules for the recipient mailbox. Then review the sending activity and event information available in Volanea, validate the sender domain configuration, and confirm that the recipient address is correct and not suppressed due to a prior bounce, complaint, unsubscribe, or manual block.
Avoid repeatedly resending the same message to an address that has bounced or complained. That can harm deliverability and may be prevented by suppression controls. The appropriate response is to inspect the event outcome and correct the underlying address, permission, or sending issue.
The from address is rejected
A rejected sender commonly means the address or domain is not authorized for the current SMTP configuration. Use an address from the verified sending domain and ensure it matches the sender rules established for the account.
Do not use an arbitrary consumer mailbox as from, such as a personal address from another provider, merely because you own it. Domain alignment and sender authentication matter for deliverability. Use a controlled domain you have configured for application sending.
Production practices for reliable SMTP delivery
A correct sendMail() call is only the start of a reliable email system. Production delivery depends on sender identity, address quality, application retries, event processing, content quality, and operational visibility.
Reuse the transporter
Create one Nodemailer transport per running process and reuse it. This avoids opening a new authenticated SMTP connection for every message. For high-throughput workers, connection pooling may be appropriate, but introduce it only after measuring throughput, provider limits, and connection behavior for your workload.
Keep credentials in a secret manager
Use a platform secret store, encrypted environment configuration, or a dedicated secrets manager. Limit access to the service that sends email. Rotate credentials on a schedule and immediately when a secret may have been exposed.
When rotating, deploy the replacement secret before revoking the old one if your account configuration allows an overlap. This reduces the risk of an avoidable email outage during deployment.
Validate inputs before composing email
Validate recipient addresses and application data before calling the mailer. Escape or safely encode user-provided values that appear in HTML. Do not concatenate untrusted input into headers such as to, cc, bcc, replyTo, or subject, and do not permit a user-controlled value to choose the sender identity.
For HTML, use an escaping function or a templating system that escapes variables by default. This is not only a security concern; malformed markup can also create messages that render badly in inboxes.
Record application-level delivery intent
Record why the email was sent: for example, password_reset_requested, invoice_issued, or team_invitation_created. Associate that event with your internal user, order, or request ID. This makes support investigations possible without storing unnecessary message content.
The SMTP result and provider event data answer different questions. The application event tells you why your system attempted the message; SMTP acceptance tells you whether the submission worked; downstream events tell you more about processing and recipient outcomes.
Next steps: webhooks, templates, and delivery operations
Once the first SMTP email is working, add delivery-aware behavior to the application. SMTP is a submission mechanism, while webhooks give your system a way to react to later events such as deliveries, bounces, complaints, opens, or clicks when those events are available for the configured sending workflow.
Process webhooks safely
A webhook endpoint should verify the authenticity of incoming requests according to the signing method configured for your account, parse the event payload, store an idempotent event record, and return a successful response quickly. Do not perform long-running customer workflows directly in the webhook request; enqueue work after durable event storage.
Make webhook processing idempotent because events can be retried. Use the provider event identifier, if supplied, or a carefully designed composite key to ensure the same event does not update your database multiple times. Treat bounces and complaints as operational signals: update your own communication rules and avoid attempting to send repeatedly to addresses that should no longer receive mail.
Move repeatable content into templates
As message volume grows, reusable templates help keep content consistent across application flows. A template can centralize layout, branding, and variable placeholders while your application supplies event-specific data such as a recipient name, invoice number, or reset URL.
Volanea supports reusable templates that can be addressed by template ID when using the REST sending workflow. That is often useful when non-engineering teams need controlled content updates or when several systems must use the same message design. With raw SMTP, your application or its template engine generally renders the complete subject and body before submission, so decide where template ownership should live before creating multiple competing sources of truth.
For transactional systems, version templates deliberately. Test a new version against controlled addresses, verify text and HTML output, validate every link, and ensure required variables fail loudly rather than producing a broken customer message.
FAQ
Can I use an SMTP API key in a Node.js application?
Yes. Store the SMTP API key as a server-side environment variable and pass it to Nodemailer through auth.pass. Keep it out of client-side code, Git repositories, logs, and public environment variables.
Do I need a Volanea-specific Node.js SDK to send with SMTP?
No. SMTP is a standard protocol, so a standard SMTP library such as Nodemailer can submit messages using the hostname, port, security settings, username, and credential provisioned for your Volanea account.
Should I set secure: true for SMTP?
Only when the SMTP configuration supplied for your account uses an immediate TLS connection. If the configuration uses STARTTLS, set secure: false and use the matching issued port. Do not guess; use the host, port, and security mode as a matched configuration.
Why should I include both text and html in an email?
The HTML version supports formatting and links, while the text version provides a readable fallback for clients or recipients that do not render HTML. Supplying both also lets Nodemailer create the appropriate multipart email structure.
Does SMTP acceptance mean the message reached the inbox?
No. It means the SMTP server accepted the message for processing. Final delivery can still be deferred, bounced, filtered, or placed outside the primary inbox. Use delivery events, sender-domain authentication, suppression handling, and controlled recipient testing to investigate outcomes.