Back to Mail
Send via code
Send mail from your application using any SMTP library. We show the Node.js / nodemailer flow because it's the most common - every other language uses the exact same fields.
Install nodemailer
terminal
npm install nodemailer
# or
pnpm add nodemailer
# or
bun add nodemailerMinimal transporter
mailer.ts
import nodemailer from "nodemailer";
export const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST!, // smtp host from the right rail
port: Number(process.env.SMTP_PORT), // 587
secure: false, // STARTTLS upgrades after connect
requireTLS: true, // refuse if TLS upgrade is unavailable
auth: {
user: process.env.SMTP_USER!, // your full email address
pass: process.env.SMTP_PASS!, // postmaster password - keep in .env
},
});Why port 587 + STARTTLS instead of 465 + secure: true? Both work. 587/STARTTLS is the modern submission standard and is what most clients pick. 465 (implicit TLS) is supported too - set
port: 465, secure: true and remove requireTLS.Send a message
send.ts
import { transporter } from "./mailer";
await transporter.sendMail({
from: '"Your App" <[email protected]>',
to: "[email protected]",
subject: "Hello from your self-hosted mail server",
text: "Plain-text body.",
html: "<p>HTML body too - clients pick whichever they prefer.</p>",
});Verify the connection first
On boot, ping the server so a broken config fails fast instead of waiting for the first sendMail to time out:
boot.ts
await transporter.verify();
console.log("SMTP ready");From other languages
The shape is identical across libraries - host, port, TLS mode, username, password.
python · smtplib
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"
msg["Subject"] = "Hello"
msg.set_content("Plain text body")
with smtplib.SMTP("smtp.yourdomain.com", 587) as s:
s.starttls()
s.login("[email protected]", "PASSWORD")
s.send_message(msg)go · net/smtp
auth := smtp.PlainAuth("",
"[email protected]",
"PASSWORD",
"smtp.yourdomain.com",
)
err := smtp.SendMail(
"smtp.yourdomain.com:587",
auth,
"[email protected]",
[]string{"[email protected]"},
[]byte("Subject: Hello\r\n\r\nbody"),
)Production checklist
- 1Never commit the password. Use environment variables loaded at runtime (Vercel/Railway/Fly all do this; locally use
.env.local). - 2Keep a single transporter instance for the lifetime of your process - opening a fresh TCP+TLS handshake per email kills throughput.
- 3Set the From address to a mailbox that exists on this server. Foreign From addresses are rejected by your SPF + DMARC policy.
- 4For transactional volume, run verify() at boot and add retries around sendMail; transient network blips are normal.
- 5Check the Health tab in your Openship admin if mail stops sending - the outbound queue lives there.