SMTP relay
Point software that already speaks SMTP at Koltrix and authenticate with an API key. Messages accepted by the relay go through the same pipeline as the REST API: the same suppression list, signing, message log, send quota and webhooks.
If you are writing new code, use the REST API instead. It returns the message id, supports idempotent retries and tells you in JSON what went wrong.
Connection settings
| Setting | Value |
|---|---|
| Host | smtp.koltrix.com |
| Port | 2525 |
| Encryption | None. The relay does not offer STARTTLS on this port; a client that insists on it gets 454. If you need an encrypted connection to Koltrix, use the REST API over HTTPS. |
| Authentication | AUTH PLAIN or AUTH LOGIN |
| Username | apikey (any value is accepted) |
| Password | An API key with the send scope |
| Message size | 25 MB per message |
| Recipients | 1,000 per message |
| Connection time | A connection is closed after 5 minutes |
Ports 587 and 465 are not the relay and don't accept API keys.
What the relay reads from your message
The relay is built for transactional mail with a simple body. It takes:
| From your SMTP session | Used as |
|---|---|
MAIL FROM (the envelope sender) | The From address of the sent message. Use an active address on a verified domain in your workspace, exactly as for the REST API. |
Each RCPT TO | A recipient. All recipients are listed in the sent message's To: header. |
The Subject: header | The subject. |
The body of a single-part text/plain or text/html message | The message body. An HTML body also gets a plain-text version generated from it. |
Everything else in the message is ignored: the From:, To:, Cc:, Bcc:
and Reply-To: headers, display names and any custom headers. Put every
recipient in RCPT TO.
Examples
swaks
The quickest way to check that a key works:
swaks --server smtp.koltrix.com --port 2525 \
--auth PLAIN --auth-user apikey --auth-password "$KOLTRIX_KEY" \
--from [email protected] --to [email protected] \
--header "Subject: Hello via SMTP" \
--body "It works."The last reply should be 250 2.0.0 OK: queued.
Node.js (nodemailer)
import nodemailer from "nodemailer";
const transport = nodemailer.createTransport({
host: "smtp.koltrix.com",
port: 2525,
secure: false,
auth: { user: "apikey", pass: process.env.KOLTRIX_KEY },
});
await transport.sendMail({
from: "[email protected]",
to: "[email protected]",
subject: "Hello via SMTP",
html: "<p>It works.</p>", // one body only: html or text, not both
});Python (smtplib)
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"
msg["Subject"] = "Hello via SMTP"
msg.set_content("<p>It works.</p>", subtype="html") # a single HTML part
with smtplib.SMTP("smtp.koltrix.com", 2525) as s:
s.login("apikey", os.environ["KOLTRIX_KEY"])
s.send_message(msg)Go (net/smtp)
smtp.PlainAuth refuses to send a password over an unencrypted connection to
anything but localhost, so Go needs a small smtp.Auth of its own:
package main
import (
"net/smtp"
"os"
)
// plainAuth is AUTH PLAIN without net/smtp's TLS requirement.
type plainAuth struct{ user, pass string }
func (a plainAuth) Start(*smtp.ServerInfo) (string, []byte, error) {
return "PLAIN", []byte("\x00" + a.user + "\x00" + a.pass), nil
}
func (a plainAuth) Next([]byte, bool) ([]byte, error) { return nil, nil }
func main() {
msg := []byte("From: [email protected]\r\n" +
"To: [email protected]\r\n" +
"Subject: Hello via SMTP\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"\r\n" +
"<p>It works.</p>\r\n")
err := smtp.SendMail("smtp.koltrix.com:2525",
plainAuth{"apikey", os.Getenv("KOLTRIX_KEY")},
"[email protected]", []string{"[email protected]"}, msg)
if err != nil {
panic(err)
}
}PHP (PHPMailer)
<?php
use PHPMailer\PHPMailer\PHPMailer;
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = "smtp.koltrix.com";
$mail->Port = 2525;
$mail->SMTPAuth = true;
$mail->SMTPAutoTLS = false;
$mail->Username = "apikey";
$mail->Password = getenv("KOLTRIX_KEY");
$mail->setFrom("[email protected]");
$mail->addAddress("[email protected]");
$mail->Subject = "Hello via SMTP";
$mail->isHTML(true);
$mail->Body = "<p>It works.</p>"; // leave AltBody empty: one part only
$mail->send();A session, line by line
S: 220 koltrix-smtp ready
C: EHLO app.acme.com
S: 250-koltrix-smtp
S: 250-AUTH PLAIN LOGIN
S: 250-8BITMIME
S: 250-SIZE 26214400
S: 250 HELP
C: AUTH PLAIN AGFwaWtleQBreF8uLi4=
S: 235 2.7.0 Authentication successful
C: MAIL FROM:<[email protected]>
S: 250 2.1.0 OK
C: RCPT TO:<[email protected]>
S: 250 2.1.5 OK
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: Subject: Hello via SMTP
C: Content-Type: text/plain; charset=UTF-8
C:
C: It works.
C: .
S: 250 2.0.0 OK: queued
C: QUIT
S: 221 2.0.0 ByeThe AUTH PLAIN argument is the base64 of a null byte, the username, a null
byte and the API key. The relay understands EHLO, HELO, AUTH, MAIL,
RCPT, DATA, RSET, NOOP and QUIT.
Reply codes
| Code | When | What to do |
|---|---|---|
235 2.7.0 | Authentication succeeded. | |
250 | Command accepted, or 250 2.0.0 OK: queued after DATA. | |
451 4.3.0 Queue failure, try again | Koltrix couldn't queue the message. | Retry later; your client normally does. |
452 4.5.3 Too many recipients | More than 1,000 RCPT TO in one message. | Send the rest in another message. |
452 4.5.3 …sending limit… | Your plan's send quota is used up (at RCPT or DATA). | Temporary on purpose: your client keeps retrying while you upgrade or the quota resets. See Limits. |
454 4.7.0 | Your client asked for STARTTLS. | Turn STARTTLS off for this connection. |
501 5.5.4 Invalid FROM address / Invalid TO address | The address couldn't be read. | Wrap it in angle brackets: MAIL FROM:<[email protected]>. |
501 5.5.2 Cannot decode credentials | The AUTH PLAIN argument isn't valid base64. | |
502 5.5.2 | Unknown command. | |
503 5.5.1 | Commands out of order (RCPT before MAIL, DATA before RCPT). | |
504 5.5.4 | An authentication method other than PLAIN or LOGIN. | |
530 5.7.0 Authentication required | MAIL FROM before AUTH. | |
535 5.7.8 Authentication failed (…) | The password isn't a kx_ key, the key is invalid or revoked, or it lacks the send scope. The text in brackets says which. | |
552 5.3.4 Message size exceeds fixed limit | The message is over 25 MB. | Permanent; retrying the same message won't help. |
554 5.6.0 Data read error | The connection broke during DATA. |
Common mistakes
- Using port 587 or 465. Those ports don't accept API keys. Use
2525. - Requiring TLS. Many libraries can be told to use STARTTLS "if available"; that works. One that requires it can't connect to the relay.
- Expecting the
From:header to be used. The relay sends from the envelope sender. Most libraries set both to the same address; check yours does. - Sending text and HTML together, or attachments. See the limitation above.
- A key without the
sendscope. Authentication fails with535 … key missing 'send' permission. Create a key withsend.