Neuctra Mailer

Documentation

Neuctra Mailer docs

Send email from any JavaScript app through your own SMTP account, and collect subscribers with a welcome email, without writing an email backend.

Introduction

Neuctra Mailer sits between your app and an SMTP account you already own, such as Gmail, Outlook, Zoho or Amazon SES. Your app calls the @neuctra/mailer SDK, Neuctra Mailer checks the request and sends it through your SMTP server.

  • Works in the browser, React, Next.js and Node.js 18 or later. The SDK has no dependencies.
  • Each app gets its own service with its own SMTP login, API key and logs.
  • Lists, templates and each list's welcome email are managed in the dashboard.

Quick start

1. Create a service

Sign in to the dashboard, click New service, and enter your SMTP host, port, username, password and From address. The service's Overview tab then shows four values: Server URL, Service ID, Service name and API key.

2. Install the SDK

Terminal
npm install @neuctra/mailer

3. Create the mailer once

lib/mailer.js
1import { NeuctraMailer } from "@neuctra/mailer";
2
3export const mailer = new NeuctraMailer({
4  serverUrl: process.env.NEXT_PUBLIC_MAILER_URL,
5  serviceId: process.env.NEXT_PUBLIC_MAILER_SERVICE_ID,
6  serviceName: process.env.NEXT_PUBLIC_MAILER_SERVICE_NAME,
7  apiKey: process.env.NEXT_PUBLIC_MAILER_API_KEY,
8});

4. Send your first email

app/page.jsx
1import { mailer } from "@/lib/mailer";
2
3await mailer.send({
4  to: "[email protected]",
5  subject: "Welcome to Acme",
6  html: "<h1>Hi Jane</h1><p>Thanks for signing up.</p>",
7});

The call resolves with the message ID once your SMTP server accepts the email. If something is wrong, the SDK throws an error that says what to fix. See Errors.

Configuration

Put the four values in environment variables. The names below are for Next.js, where the NEXT_PUBLIC_ prefix makes them available in the browser. Any names work.

.env.local
1NEXT_PUBLIC_MAILER_URL=https://your-mailer-api.example.com
2NEXT_PUBLIC_MAILER_SERVICE_ID=your-service-id
3NEXT_PUBLIC_MAILER_SERVICE_NAME=your-service-name
4NEXT_PUBLIC_MAILER_API_KEY=your-api-key

Lock the key to your website

Code that runs in the browser exposes the API key. Add your website to the service's allowed origins so no other site can use it. See Security.

new NeuctraMailer(config)

Creates a client for one service. Create it once and import it wherever you send email.

OptionTypeDescription
serverUrlstringURL of the Neuctra Mailer API. A trailing slash is removed.
serviceIdstringFrom the service's Overview tab.
serviceNamestringMust match the service's name. Case does not matter.
apiKeystringThe public key (nm_...) in browser code, or the server key (nm_sk_...) on a server. Both are on the Overview tab.

All four are required. If any is missing, the constructor throws a NeuctraMailerError with status 0 that names it.

mailer.send(mail)

Sends one email through the service's SMTP server.

FieldTypeDescription
tostring | string[]Required. One address, a comma-separated string, or an array.
subjectstringRequired.
htmlstringHTML body. Provide html, text, or both.
textstringPlain-text body. Provide html, text, or both.
ccstring | string[]Optional.
bccstring | string[]Optional.
replyTostringOptional. One address.
captchaTokenstringCloudflare Turnstile token. Required with the public key when the service's captcha is on.
send.js
1const result = await mailer.send({
2  to: ["[email protected]", "[email protected]"], // or "[email protected], [email protected]"
3  subject: "Order #1042 confirmed",
4  html: "<h1>Thanks for your order</h1>",
5  text: "Thanks for your order",              // plain-text version, optional
6  replyTo: "[email protected]",
7  bcc: "[email protected]",
8});
9
10// { success: true, messageId: "<...>", accepted: ["[email protected]", ...], rejected: [] }

The email can have at most 50 recipients across to, cc and bcc, and every address must be valid. The From address is the one saved on the service.

mailer.subscribe(options)

Adds someone to one of your email lists. If the list's welcome email is on, it is sent before the call returns, and welcomeSent in the result says whether it went out.

FieldTypeDescription
listIdstringRequired. The newsletter's id, from the Newsletters tab. Email lists can't be subscribed to.
emailstringRequired.
namestringOptional. Up to 100 characters.
captchaTokenstringCloudflare Turnstile token, when the service's captcha is on.
subscribe.js
1const { alreadySubscribed } = await mailer.subscribe({
2  listId: "cm_newsletter",
3  email: "[email protected]",
4  name: "Jane",
5});

Returns { success: true, alreadySubscribed }. When alreadySubscribed is true the address was already on the list, and no second welcome email is sent.

mailer.unsubscribe(options)

Removes someone from a list. The answer is the same whether or not the address was on the list, so the endpoint cannot be used to check who is subscribed. Every marketing email also carries its own unsubscribe link.

unsubscribe.js
1await mailer.unsubscribe({ listId: "cm_newsletter", email: "[email protected]" });
2// { success: true }

mailer.verify()

Checks your credentials and logs in to the service's SMTP server without sending anything. Call it once while setting up, or in a health check.

verify.js
1const { service } = await mailer.verify();
2console.log(`Connected to ${service.name}`);

Errors

Every method returns a promise. On failure it throws a NeuctraMailerError with a readable message from the server and the HTTP status.

errors.js
1import { NeuctraMailerError } from "@neuctra/mailer";
2
3try {
4  await mailer.send({ to, subject, html });
5} catch (err) {
6  if (err instanceof NeuctraMailerError) {
7    console.error(err.status, err.message);
8  }
9}
StatusMeaning
0The API could not be reached, or the config is missing a value. Check serverUrl.
400Something is wrong with the request: no recipient or subject, no body, an invalid address, or more than 50 recipients.
401Wrong apiKey or serviceId, or serviceName does not match the service.
403The service is turned off, the website isn't allowed, the recipient isn't allowed for the public key, the captcha failed, or a server key was used from a browser.
404The list does not exist or belongs to another service.
429Too many emails: the service's per-minute or per-day rate limit (with a Retry-After header), or your plan's monthly emails or contacts. The message says which.
502The SMTP server refused. The message says why, for example a wrong password or port.

Contact form

A React form that emails you each message. replyTo is set to the visitor's address, so you can answer straight from your inbox.

components/contact-form.jsx
1"use client";
2
3import { useState } from "react";
4import { mailer } from "@/lib/mailer";
5
6export function ContactForm() {
7  const [status, setStatus] = useState("idle");
8
9  async function handleSubmit(event) {
10    event.preventDefault();
11    const form = new FormData(event.currentTarget);
12    setStatus("sending");
13    try {
14      await mailer.send({
15        to: "[email protected]",
16        replyTo: form.get("email"),
17        subject: `New message from ${form.get("name")}`,
18        text: form.get("message"),
19      });
20      setStatus("sent");
21    } catch {
22      setStatus("error");
23    }
24  }
25
26  return (
27    <form onSubmit={handleSubmit}>
28      <input name="name" required />
29      <input name="email" type="email" required />
30      <textarea name="message" required />
31      <button disabled={status === "sending"}>Send</button>
32      {status === "sent" && <p>Thanks, we will reply soon.</p>}
33      {status === "error" && <p>Something went wrong. Please try again.</p>}
34    </form>
35  );
36}

Newsletter signup

Create a newsletter in the Newsletters tab, then call subscribe from your signup form. Pick a welcome email template for the list and turn it on, and every new subscriber gets it right away.

lib/newsletter.js
1import { mailer } from "@/lib/mailer";
2
3export async function joinNewsletter(email) {
4  const { alreadySubscribed } = await mailer.subscribe({
5    listId: process.env.NEXT_PUBLIC_MAILER_LIST_ID,
6    email,
7  });
8  return alreadySubscribed ? "You are already on the list." : "Check your inbox for a welcome email.";
9}

Sending from a server

The SDK also runs on the server: Next.js route handlers, server actions, or any Node.js 18+ code. The API key then stays out of the browser.

app/api/order-confirmation/route.js
1// app/api/order-confirmation/route.js
2import { NeuctraMailer } from "@neuctra/mailer";
3
4// No NEXT_PUBLIC_ prefix, so the key never reaches the browser.
5const mailer = new NeuctraMailer({
6  serverUrl: process.env.MAILER_URL,
7  serviceId: process.env.MAILER_SERVICE_ID,
8  serviceName: process.env.MAILER_SERVICE_NAME,
9  apiKey: process.env.MAILER_API_KEY,
10});
11
12export async function POST(request) {
13  const { email, orderId } = await request.json();
14  const { messageId } = await mailer.send({
15    to: email,
16    subject: `Order #${orderId} confirmed`,
17    html: `<p>Order #${orderId} is on its way.</p>`,
18  });
19  return Response.json({ messageId });
20}

Use the server key on servers

Create a server key under Settings, Security and put it in MAILER_API_KEY. It isn't limited by allowed websites, allowed recipients or the captcha, so it can send order emails to any customer. It is refused from a browser, and the SDK won't start with it in browser code.

Templates and sending

Templates, welcome emails and one-off sends are managed in the dashboard. Your code only adds and removes subscribers.

  • Templates. Build emails from blocks (headings, text, buttons, images, dividers) or paste your own HTML. Start from a ready-made design and preview on desktop and mobile.
  • Welcome email. Choose a template for a list and turn it on. Everyone who subscribes from your app gets it immediately. Contacts you add in the dashboard don't.
  • Send tab. Pick a list (or import emails from a paste or a CSV file), then send to one person with the row's Send button or tick several and send to all of them. Each contact shows Not sent, Sent or Failed for that template and remembers it, so you can finish later.
  • Every welcome and Send tab email includes an unsubscribe link.
  • Do-not-send list. Each service keeps one. People who unsubscribe, or whose address bounces, are added by themselves and never get Send tab or welcome emails again, from any list. You can add or remove addresses in the Newsletters tab. SDK emails aren't blocked, since those are usually receipts and password resets.
  • Warm-up. For a new service's first 3 weeks, the Send tab sends at most 50 emails a day, rising to 100, 250 and 500, so mail providers learn to trust a new sender. Turn it off under Settings, Security.

Read more in the email template builder guide.

REST API

The SDK is a thin wrapper over four HTTP endpoints, so any language can call them. Send the service's credentials as headers and a JSON body.

EndpointBody
POST /api/mail/send{ to, subject, html?, text?, cc?, bcc?, replyTo? }
POST /api/mail/subscribe{ listId, email, name? }
POST /api/mail/unsubscribe{ listId, email }
GET /api/mail/verifyNo body.
HeaderValue
x-api-keyThe service's API key.
x-service-idThe service ID.
x-service-nameThe service name.
Content-Typeapplication/json
Terminal
1curl -X POST "$MAILER_URL/api/mail/send" \
2  -H "Content-Type: application/json" \
3  -H "x-api-key: $MAILER_API_KEY" \
4  -H "x-service-id: $MAILER_SERVICE_ID" \
5  -H "x-service-name: $MAILER_SERVICE_NAME" \
6  -d '{"to":"[email protected]","subject":"Hello","text":"Sent with curl"}'

Errors come back as { "error": "message" } with the status codes listed under Errors.

SMTP providers

Any SMTP account works. These are the settings for common providers.

HostPortNotes
smtp.gmail.com465Gmail and Google Workspace. Use an App Password, not your normal password.
smtp.office365.com587Outlook and Microsoft 365.
smtp.zoho.com465Zoho Mail.
email-smtp.us-east-1.amazonaws.com587Amazon SES. Use your region's host and SMTP credentials.
smtp-relay.brevo.com587Brevo.
smtp.mailgun.org587Mailgun.

Port 465 uses SSL from the start, so turn on the SSL switch. Ports 587 and 25 start plain and upgrade to TLS, so leave it off. Click Check connection on the service's SMTP tab to test the settings.

Security

Anyone can read a key that runs in a browser, and a script can fake the website a request claims to come from. So each service has two keys and a few limits, set under Settings, Security.

KeyPrefixUse
Public keynm_Browser code. Limited by allowed websites, allowed recipients, the captcha and rate limits.
Server keynm_sk_Your server only. Rate limited, can email anyone, and refused from a browser.
  • Allowed recipients. For a contact form, list only your own address. A copied public key then can't email anyone else.
  • Rate limits. Emails per minute and per day for each service (defaults 30 and 500), counting every recipient. Keep the daily number at or under your SMTP provider's limit.
  • Captcha. Turn on Cloudflare Turnstile, add its secret key, put the widget in your form, and pass its token as captchaToken to send or subscribe.
  • Allowed websites. List the sites that may use the public key, such as https://acme.com. This stops other websites, but not scripts, which is why the limits above matter.
  • Rotate the key. Click Rotate key in the service's settings to get a new API key. The old key stops working immediately.
  • Turn a service off. Switch off Accepting emails in the service's settings, and every request is refused until you switch it back on.
  • SMTP passwords and API keys are stored encrypted with AES-256-GCM.

Limits

LimitValue
Recipients per email50 across to, cc and bcc
Request body1 MB
Contacts added at once100 per request in the dashboard
Template40 blocks

Your plan also sets how many emails you can send each month, how many subscribed contacts and services you can have, how many newsletters, email lists and templates each service can have, and how many contacts each service can send to per day from the dashboard's Send tab. Each recipient counts as one email. Emails sent with the SDK only count toward the monthly emails. See pricing for each plan's limits, and Plan & usage in the dashboard for what you have used.

Your SMTP provider has its own daily sending limits. Gmail, for example, limits how many emails an account can send per day.

MCP server for AI tools

The Neuctra Mailer MCP server gives AI assistants such as Claude Code, Claude Desktop, Cursor and VS Code the SDK reference, so they write correct Neuctra Mailer code. With a service's credentials it can also verify the service, send email and manage list subscribers.

Claude Code
claude mcp add neuctra-mailer -- npx -y @neuctra/mailer-mcp

For other clients, add it to the MCP config. Leave out env if you only want the docs tools.

mcp.json
1{
2  "mcpServers": {
3    "neuctra-mailer": {
4      "command": "npx",
5      "args": ["-y", "@neuctra/mailer-mcp"],
6      "env": {
7        "NEUCTRA_MAILER_SERVER_URL": "https://your-mailer-api.example.com",
8        "NEUCTRA_MAILER_SERVICE_ID": "your-service-id",
9        "NEUCTRA_MAILER_SERVICE_NAME": "your-service-name",
10        "NEUCTRA_MAILER_API_KEY": "your-api-key"
11      }
12    }
13  }
14}