SMTP Submission
In addition to the REST API, Altermail provides a standard SMTP submission server. Use it to send email directly from your application, migrate from another ESP without code changes, or use any SMTP-compatible library (Nodemailer, PHPMailer, Python's smtplib, etc.).
Using WordPress?
No plugin or theme code required. Install an SMTP plugin — WP Mail SMTP, Post SMTP, Easy WP SMTP, and FluentSMTP all work — and enter the connection settings below into its “Other SMTP” setup screen: host, port 2525, STARTTLS, then your domain (or apikey) as the username and your API key as the password. wp_mail() calls from any plugin or theme, including WooCommerce order emails and contact forms, route through Altermail automatically from that point on.
Connection settings
| Host | smtp.altermail-console.com.ng |
| Port | 2525 |
| Encryption | STARTTLS (required in production) |
| Username | Your verified sender domain (e.g. yourdomain.com) or the literal string apikey |
| Password | Your Altermail API key |
Username: domain vs apikey
The SMTP username controls whether domain ownership is verified at connection time:
- •Username = your domain (e.g.
yourdomain.com): Altermail verifies during AUTH that this domain is a verified sender on your account. TheMAIL FROMaddress must also use this domain; any mismatch is rejected immediately. DKIM signing is loaded for the session if the domain has an active DKIM key. - •Username =
apikey(literal string): No domain is pre-checked at AUTH. You can use any verified domain inMAIL FROM, and domain ownership is verified at send time. DKIM signing is not loaded for the session.
Using your domain as the username is recommended: it loads DKIM signing for the session and enforces sender domain ownership at AUTH rather than at relay time.
DKIM signing
When you use your domain as the SMTP username and that domain has an active DKIM key set up in the Domains page, your outgoing messages are automatically signed with RSA-SHA256. No extra parameters are needed; signing happens server-side, exactly the same as the REST API.
If DKIM signing fails for any reason (e.g. a key format issue), the message is still delivered unsigned. Signing is non-fatal and will never cause a delivery failure.
Quota & billing
SMTP sends consume quota from the same pool as REST API sends; they are not separate. Quota is deducted before the message is relayed to the mail server. Each recipient address counts as one credit, identical to the API.
452 4.2.1 and the message is not sent. Your SMTP client will surface this as a send error. Buy a PAYG bundle or upgrade your plan from the Billing page.Delivery event tracking
Every message accepted via SMTP is tracked in the same delivery events pipeline as the REST API. When the server accepts your message (250 OK), it returns an Altermail-assigned messageId in the response text:
250 OK messageId=5a4911ad-9cdb-4cb4-bf74-a317927cd344With Nodemailer, read it from info.response. Store this value to look up events later via GET /v1/user/email/events?messageId=.... Bounce and delivery webhooks reference the same id.
Rate limits by plan
Three separate limits apply per account. All three scale with your plan.
| Plan | Concurrent connections | Messages per session | Sends per minute |
|---|---|---|---|
| Free | 2 | 10 | 10/min |
| Developer | 5 | 50 | 30/min |
| Growth | 10 | 100 | 60/min |
| Business | 25 | 250 | 120/min |
| Enterprise | 50 | 500 | 300/min |
Exceeding concurrent connections returns 421 4.4.5. Hitting the messages-per-session cap returns 421 4.4.3 and requires a reconnect. Sending too fast returns 421 4.4.5 and clears after 60 seconds.
Other limitations
- •50 recipients per message: The total number of To + CC + BCC addresses per message. Exceeded recipients are rejected with 452 4.5.3 before DATA is processed.
- •25 MB message size: The maximum raw message size including headers, body, and attachments. Oversized messages are rejected with 552 5.3.4.
- •Concurrent connection limit: The number of simultaneous open SMTP connections your account can hold depends on your plan (2 on Free, up to 50 on Enterprise). Exceeding this returns 421 4.4.5.
- •Messages per session: Each connection can send a limited number of messages before you must reconnect (10 on Free, up to 500 on Enterprise). This is standard SMTP behaviour shared by all major providers.
- •Per-minute send rate: Throughput is capped per minute by your plan (10/min on Free, up to 300/min on Enterprise), matching the same tiers as the REST API rate limit. Only successfully relayed messages count toward the window.
- •STARTTLS required in production: The server advertises STARTTLS. If TLS certificates are loaded, plaintext AUTH is not allowed. Always configure your client to use STARTTLS.
- •Delivery events tracked per primary recipient only: The messageId is logged against the first To: address. Bounce and delivery tracking works the same as the API. The assigned messageId is returned in the 250 response so you can correlate events later.
- •Unsubscribe suppression and email category: Add an X-Altermail-Category header set to "transactional" or "marketing" before sending. Transactional emails (OTPs, receipts, account alerts) are always delivered regardless of unsubscribe status. Marketing emails are blocked for recipients who have unsubscribed, quota is still deducted, and a soft bounce event is recorded. If the header is omitted, Altermail auto-detects the category from the Subject line.
SMTP error codes
These are the standard SMTP responses your client will receive when something goes wrong. 4xx codes are transient (retry later); 5xx codes are permanent (fix the issue first).
| Code | Meaning | Fix |
|---|---|---|
535 5.7.8 | AUTH failed | Wrong API key, suspended account, or unverified domain. |
421 4.4.5 | Too many connections | Your account has reached its concurrent connection limit. Close an existing connection first. |
530 5.7.0 | Auth required | MAIL FROM issued without prior AUTH. |
553 5.1.3 | Sender domain mismatch | MAIL FROM domain differs from the domain used as username. |
421 4.4.3 | Message limit reached | Your account hit its messages-per-session limit. Reconnect to continue sending. |
452 4.5.3 | Too many recipients | More than 50 RCPT TO addresses in one message. |
552 5.3.4 | Message too large | Raw message exceeds the 25 MB limit. |
421 4.4.5 | Sending too fast | Per-minute send rate exceeded for your plan. Slow down and retry. |
452 4.2.1 | Quota exceeded | Monthly or daily email limit reached, or no PAYG credits remaining. |
421 4.4.1 | Relay failure | Transient error relaying to the mail server, retry later. |
Code examples
Replace YOUR_API_KEY with your API key from the Overview page and yourdomain.com with a verified sender domain.
# curl supports SMTP natively, useful for quick testing
curl smtp://smtp.altermail-console.com.ng:2525 \
--ssl-reqd \
--user "yourdomain.com:YOUR_API_KEY" \
--mail-from "hello@yourdomain.com" \
--mail-rcpt "recipient@example.com" \
--upload-file - << 'EOF'
From: hello@yourdomain.com
To: recipient@example.com
Subject: Hello from Altermail SMTP
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Hello! This is a test email sent over SMTP.
EOFimport nodemailer from "nodemailer";
const transport = nodemailer.createTransport({
host: "smtp.altermail-console.com.ng",
port: 2525,
secure: false, // use STARTTLS (not direct SSL)
auth: {
user: "yourdomain.com", // your verified sender domain, or "apikey" if you skip domain enforcement
pass: "YOUR_API_KEY",
},
});
await transport.sendMail({
from: '"Your Name" <hello@yourdomain.com>',
to: "recipient@example.com",
subject: "Hello from Altermail SMTP",
html: "<h1>Hello!</h1><p>This is a test email sent over SMTP.</p>",
text: "Hello! This is a test email sent over SMTP.",
});import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
msg = MIMEMultipart("alternative")
msg["Subject"] = "Hello from Altermail SMTP"
msg["From"] = "hello@yourdomain.com"
msg["To"] = "recipient@example.com"
msg.attach(MIMEText("Hello! This is a test email sent over SMTP.", "plain"))
msg.attach(MIMEText("<h1>Hello!</h1><p>This is a test email sent over SMTP.</p>", "html"))
with smtplib.SMTP("smtp.altermail-console.com.ng", 2525) as server:
server.ehlo()
server.starttls()
server.ehlo()
server.login("yourdomain.com", "YOUR_API_KEY")
server.sendmail(
"hello@yourdomain.com",
["recipient@example.com"],
msg.as_string(),
)<?php
// Requires PHPMailer: composer require phpmailer/phpmailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.altermail-console.com.ng';
$mail->SMTPAuth = true;
$mail->Username = 'yourdomain.com'; // verified domain or "apikey"
$mail->Password = 'YOUR_API_KEY';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 2525;
$mail->setFrom('hello@yourdomain.com', 'Your Name');
$mail->addAddress('recipient@example.com');
$mail->isHTML(true);
$mail->Subject = 'Hello from Altermail SMTP';
$mail->Body = '<h1>Hello!</h1><p>This is a test email sent over SMTP.</p>';
$mail->AltBody = 'Hello! This is a test email sent over SMTP.';
$mail->send();package main
import (
"crypto/tls"
"net/smtp"
"strings"
)
func main() {
host := "smtp.altermail-console.com.ng"
port := "2525"
username := "yourdomain.com" // verified domain, or "apikey"
password := "YOUR_API_KEY"
// Dial and upgrade to TLS
conn, err := smtp.Dial(host + ":" + port)
if err != nil { panic(err) }
defer conn.Close()
if err = conn.StartTLS(&tls.Config{ServerName: host}); err != nil {
panic(err)
}
if err = conn.Auth(smtp.PlainAuth("", username, password, host)); err != nil {
panic(err)
}
msg := strings.Join([]string{
"From: hello@yourdomain.com",
"To: recipient@example.com",
"Subject: Hello from Altermail SMTP",
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=utf-8",
"",
"Hello! This is a test email sent over SMTP.",
}, "\r\n")
if err = smtp.SendMail(host+":"+port,
smtp.PlainAuth("", username, password, host),
"hello@yourdomain.com",
[]string{"recipient@example.com"},
[]byte(msg),
); err != nil {
panic(err)
}
}using System.Net;
using System.Net.Mail;
var client = new SmtpClient("smtp.altermail-console.com.ng", 2525)
{
Credentials = new NetworkCredential("yourdomain.com", "YOUR_API_KEY"),
EnableSsl = true, // STARTTLS
};
var msg = new MailMessage
{
From = new MailAddress("hello@yourdomain.com", "Your Name"),
Subject = "Hello from Altermail SMTP",
Body = "<h1>Hello!</h1><p>This is a test email sent over SMTP.</p>",
IsBodyHtml = true,
};
msg.To.Add("recipient@example.com");
await client.SendMailAsync(msg);import java.util.Properties;
import jakarta.mail.*;
import jakarta.mail.internet.*;
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.altermail-console.com.ng");
props.put("mail.smtp.port", "2525");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
// use your verified domain as username, or "apikey"
return new PasswordAuthentication("yourdomain.com", "YOUR_API_KEY");
}
});
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("hello@yourdomain.com", "Your Name"));
msg.setRecipient(Message.RecipientType.TO,
new InternetAddress("recipient@example.com"));
msg.setSubject("Hello from Altermail SMTP");
msg.setText("Hello! This is a test email sent over SMTP.");
Transport.send(msg);#include <curl/curl.h>
#include <string>
// Requires libcurl with SSL support
// Compile: g++ -o send send.cpp -lcurl
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
const std::string raw_email =
"From: hello@yourdomain.com\r\n"
"To: recipient@example.com\r\n"
"Subject: Hello from Altermail SMTP\r\n"
"MIME-Version: 1.0\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"\r\n"
"Hello! This is a test email sent over SMTP.\r\n";
struct curl_slist* rcpts = curl_slist_append(nullptr, "recipient@example.com");
curl_easy_setopt(curl, CURLOPT_URL, "smtp://smtp.altermail-console.com.ng:2525");
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
curl_easy_setopt(curl, CURLOPT_USERNAME, "yourdomain.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "YOUR_API_KEY");
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, "<hello@yourdomain.com>");
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpts);
curl_easy_setopt(curl, CURLOPT_READDATA, (void*)&raw_email);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_perform(curl);
curl_slist_free_all(rcpts);
curl_easy_cleanup(curl);
return 0;
}SMTP vs REST API: which should I use?
| REST API | SMTP | |
|---|---|---|
| Ease of use | Simple HTTP call from any language | Requires SMTP library / config |
| Templates | Supported (active template used automatically) | Not supported, send raw MIME |
| Attachments | Base64 in JSON body | Native MIME, use your library's attachment API |
| DKIM signing | Automatic | Automatic when domain is the username |
| Delivery events | Full tracking + webhooks | Full tracking + webhooks |
| Migration from another ESP | Requires code changes | Change host/port/credentials only |
| Rate limit | 60 sends/min per token | Per-minute cap by plan (10/min Free, up to 300/min Enterprise) |
| Best for | New integrations, programmatic sends | ESP migrations, SMTP-native frameworks |