KoltrixDocs

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

SettingValue
Hostsmtp.koltrix.com
Port2525
EncryptionNone. 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.
AuthenticationAUTH PLAIN or AUTH LOGIN
Usernameapikey (any value is accepted)
PasswordAn API key with the send scope
Message size25 MB per message
Recipients1,000 per message
Connection timeA 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 sessionUsed 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 TOA recipient. All recipients are listed in the sent message's To: header.
The Subject: headerThe subject.
The body of a single-part text/plain or text/html messageThe 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)

Node.js
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)

Python
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:

Go
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
<?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 Bye

The 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

CodeWhenWhat to do
235 2.7.0Authentication succeeded.
250Command accepted, or 250 2.0.0 OK: queued after DATA.
451 4.3.0 Queue failure, try againKoltrix couldn't queue the message.Retry later; your client normally does.
452 4.5.3 Too many recipientsMore 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.0Your client asked for STARTTLS.Turn STARTTLS off for this connection.
501 5.5.4 Invalid FROM address / Invalid TO addressThe address couldn't be read.Wrap it in angle brackets: MAIL FROM:<[email protected]>.
501 5.5.2 Cannot decode credentialsThe AUTH PLAIN argument isn't valid base64.
502 5.5.2Unknown command.
503 5.5.1Commands out of order (RCPT before MAIL, DATA before RCPT).
504 5.5.4An authentication method other than PLAIN or LOGIN.
530 5.7.0 Authentication requiredMAIL 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 limitThe message is over 25 MB.Permanent; retrying the same message won't help.
554 5.6.0 Data read errorThe 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 send scope. Authentication fails with 535 … key missing 'send' permission. Create a key with send.