How to Test HTML Emails with Ethereal Accounts
Send HTML email through Ethereal, inspect Nodemailer’s preview, and understand account limits, troubleshooting, and what the preview can verify.
To test an HTML email with Ethereal, send it through an Ethereal SMTP account and inspect the captured message. In Nodemailer, create a test account with createTestAccount(), configure the SMTP transport with the returned credentials, put your markup in the message’s html field, and call getTestMessageUrl(info) after sending. Ethereal captures the outgoing message for review; it does not deliver it to the recipient address.
1. Create an Ethereal account and send a test email
This complete Node.js example creates a temporary test account, sends both text and HTML content, and prints the preview URL. Install Nodemailer first:
npm install nodemailer
Save as send-test-email.js and run with node send-test-email.js:
const nodemailer = require('nodemailer');
async function main() {
const account = await nodemailer.createTestAccount();
const transporter = nodemailer.createTransport({
host: account.smtp.host,
port: account.smtp.port,
secure: account.smtp.secure,
auth: {
user: account.user,
pass: account.pass,
},
});
const info = await transporter.sendMail({
from: 'Email Preview <sender@example.com>',
to: 'review@example.com',
subject: 'HTML email preview',
text: 'This is the plain-text version of the email.',
html: `
<!doctype html>
<html>
<body style="font-family: Arial, sans-serif">
<h1>Hello from the test environment</h1>
<p>This message was captured by Ethereal.</p>
<p><a href="https://example.com">Example link</a></p>
</body>
</html>
`,
});
console.log('Message ID:', info.messageId);
console.log('Preview URL:', nodemailer.getTestMessageUrl(info));
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The account’s returned SMTP settings are the source of truth for this connection. Ethereal’s help page lists smtp.ethereal.email, port 587, and STARTTLS. The example uses the generated account’s host, port, and secure values so it follows the account response. See the Ethereal homepage and SMTP/IMAP/POP3 help.
What the example does
createTestAccount()obtains credentials for an Ethereal test mailbox.createTransport()configures Nodemailer to submit through that mailbox’s SMTP service.sendMail()sends the message object. Thehtmlproperty contains the markup to inspect;textsupplies a plain-text alternative.getTestMessageUrl(info)turns the send result into a link to the captured message.
The to field can be a real-looking or made-up address for this capture workflow. Ethereal does not deliver the message to that recipient. Do not use this setup to test actual delivery or inbound receiving.
2. Inspect the captured message
Open the printed preview URL in a browser to inspect the message Ethereal captured. You can also use the Ethereal mailbox view or connect with an IMAP or POP3 client. The preview URL contains access information, so treat it like a private link: avoid posting it publicly or committing it to source control.
A captured preview confirms that the message reached Ethereal and lets you inspect its stored content. It does not by itself prove how every real email client will render the message. For client-specific rendering checks, use the appropriate email-client testing workflow as well.
3. Choose account and connection settings
| Setting or choice | How to use it |
|---|---|
| Generated credentials | Use the host, port, secure flag, username, and password returned by createTestAccount(). Keep the credentials out of committed code. |
| SMTP endpoint | The help page lists smtp.ethereal.email, port 587, with STARTTLS. Check the current help page if you configure a transport manually. |
| Account creation | Create accounts with Nodemailer’s createTestAccount() or through the Ethereal website. For a reproducible script, account creation from the script keeps setup together. |
| Account reuse | A single account can normally be reused. Ethereal says account details requested from the same process are cached in memory. Reuse avoids creating a new account for each message. |
| Preview access | Use Nodemailer’s getTestMessageUrl(info) for a direct link, or inspect the mailbox through the website or an IMAP/POP3 client. |
The Ethereal homepage example calls for Nodemailer v4.1.0 or newer. Use the current Nodemailer documentation and the API supported by the version installed in your project when adapting the example.
4. Test the HTML that matters
- Include a text part. Provide both
textandhtmlso you can inspect the message alternatives and avoid making the test depend on HTML alone. - Use representative content. Test realistic subjects, links, image references, and long or short copy. The capture shows what was submitted to Ethereal.
- Generate HTML with your real template code. Import the same renderer or template function your application uses, then pass its output as the
htmlvalue. - Inspect the result, not only the send result. A resolved
sendMail()call and a preview URL indicate capture workflow progress; review the message content in the preview. - Separate capture from rendering validation. Ethereal is a safe outbound sandbox. Its preview is not a claim of cross-client compatibility.
5. Limits, reliability, and cost
Ethereal is intended for development capture, not production delivery. Public-account inbound mail is disabled, and messages sent to public Ethereal addresses bounce. The FAQ says captured messages remain for a few hours before deletion, each address has a 100 MB quota, and public accounts can send up to 200 messages per hour. These limits make Ethereal suitable for short-lived development checks, but not for retaining test artifacts indefinitely or running an unbounded sending workload. See the Ethereal FAQ.
The help page describes inbound-capable accounts generated with Nodemailer v6.9.14 or newer when ETHEREAL_API_KEY is set; that key is tied to an active EmailEngine subscription. This is a separate requirement from the basic public outbound-capture workflow. Check Ethereal’s current help and FAQ for the account mode and limits that apply to your setup.
Ethereal is a development sandbox; the cited pages do not position it as a production email provider. For production delivery, configure an email service intended to deliver messages and keep that configuration separate from test credentials. The research sources do not establish a price for Ethereal, so confirm any account or related service terms directly with its provider.
6. Troubleshooting
| Symptom | Likely cause | What to check or do |
|---|---|---|
| Connection refused or timeout | Wrong host or port, network restrictions, or transport settings that do not match the generated account. | Use the returned SMTP host, port, and secure values. For manual settings, confirm the current Ethereal help page’s host and STARTTLS details, and check whether the execution environment permits outbound SMTP. |
| Authentication failed | Credentials were mistyped, stale, or taken from a different account. | Use the user and pass from the same account object as the SMTP settings. Create or retrieve an account again if needed, and avoid trimming or altering credentials. |
| No preview URL appears | The send failed before returning info, or the code did not call getTestMessageUrl(). |
Handle the send error, then pass the successful info result to nodemailer.getTestMessageUrl(info). Keep the URL output visible in your terminal or logs. |
| Preview link no longer works | The message or temporary account data may have expired; Ethereal says captured messages are retained for only a few hours. | Send the message again and save any content you need to retain outside the temporary preview. |
| Mail to an Ethereal address bounces | Public Ethereal accounts do not accept inbound mail. | Use Ethereal for outbound message capture. Inbound-capable account creation has separate requirements described in the official help page. |
| Unexpected HTML in the preview | The template output may be escaped, empty, or not passed as the html property. |
Log or inspect the generated HTML string before sending, verify the message object uses html: renderedMarkup, and ensure the template renderer returns a string. |
| Some images or styles do not appear as expected | The captured message preview does not establish rendering behavior across all email clients. | Check the generated markup and referenced assets, then validate in the actual email clients relevant to your users. |
| Sending is rejected after repeated test messages | A public account may have reached the published limit of 200 messages per hour. | Reduce test volume, reuse an account appropriately, and confirm current limits in the FAQ. |
7. Or skip the browser setup
If your email workflow also needs a screenshot of a web page, [ScreenshotNeo](https://screenshotneo.com) offers a website screenshot API and MCP server. It does not replace Ethereal’s SMTP capture: use Ethereal to capture outbound email, and use ScreenshotNeo when the thing you need to inspect is a web page or rendered HTML page.
One GET request returns an image or PDF. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the screenshot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. These are website captures, while Ethereal remains the tool in this guide for safely capturing test email.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.
8. Frequently asked questions
Where is the message preview URL?
Print the value of nodemailer.getTestMessageUrl(info) after sendMail() succeeds. Treat that URL as sensitive because it provides access to the preview.
Should I generate a new account for every message?
Usually, no. Ethereal says account details are cached in memory when accounts are requested from the same process, and an account can normally be reused. Create another when you need isolation or the existing account is no longer usable.
Can I send to a real address to check delivery?
No. Ethereal captures the test message and does not deliver it to the recipient. Use a production delivery provider for delivery testing, with controls appropriate to your environment.
Can Ethereal verify how the message looks in Gmail or Outlook?
The preview lets you inspect the captured message. The official Ethereal sources do not say that this validates rendering in every real-world email client, so use client-specific rendering checks when that matters.
Can I keep the preview indefinitely?
No. Ethereal’s FAQ describes message retention as a few hours. Save any artifacts you need to retain in your own development workflow.


