SMTP relay

Use the relay to keep your existing libraries while gaining wemail's deliverability and analytics.

Prefer SMTP? Use the relay to keep your existing libraries while gaining wemail's deliverability and analytics. SMTP and the REST API use the same delivery pipeline, message statuses, event history and webhooks.

Connection

ParameterTypeRequiredDescription
HoststringOptionalsmtp.wemail.io
PortintegerOptional587 (STARTTLS) · 465 (TLS) · 2525 (fallback). STARTTLS is required in production — plaintext authentication is refused.
UsernamestringOptionalConventionally your verified sender domain (any value is accepted — authorization is by API key and the From domain).
PasswordstringOptionalA full_access or send_only API key. read_only keys are rejected at authentication (they cannot send).
Test keysafn_test_OptionalA test key authenticates normally but the send is SIMULATED: no real delivery, synthetic events per the key's configured outcome mix, metered against the free monthly test allowance.

Sending rules

ParameterTypeRequiredDescription
From domainverifiedRequiredThe From address's domain must be registered AND verified for your account — otherwise the message is rejected. An authenticated key cannot spoof other domains.
From namedisplay nameOptionalA display name in the From header is honored end-to-end: send From: "Acme Support" <noreply@acme.io> and recipients see exactly that name. Only the address part is validated against your verified domains.
RecipientsenvelopeOptionalDelivery follows the SMTP envelope (RCPT TO), so BCC works exactly like normal mail: recipients whose header was stripped by your client are still delivered. Pure-BCC sends (no visible To) are supported. Each envelope recipient is tracked as its own message.
QuotameteredOptionalEvery envelope recipient (To + Cc + Bcc) consumes one send unit against your monthly/daily plan quota — identical to the REST API. Exhausted quota rejects the send.
Message sizelimitOptionalUp to 20 MB on the wire (headers + bodies + encoded attachments), each attachment up to 5 MB decoded. Oversized messages are rejected during transfer.
AttachmentsMIMEOptionalRegular MIME attachments are fully supported and delivered as-is — attach files the way your library normally does.
Reply-To / Cc / custom headerspassthroughOptionalReply-To is honored; Cc recipients are delivered and tracked; all other headers pass through to the recipient, and X-* headers are echoed back in webhook payloads.

Custom X-* headers

Any X-* MIME header on a submitted message — e.g. a correlation id like X-TES-MSGID: tes-7f3a9c — round-trips end-to-end, exactly like the REST API's headers field: it is stored with the message, stamped on the delivered email, echoed on every webhook payload for the message, and returned by GET /v3/messages/{id} (it also appears in the Console message drawer). The first 32 X-* headers round-trip, with values up to 1 KB each. X-Wemail-Tag, X-Wemail-Variables and X-Wemail-Template are control headers, not correlation headers — they are consumed at submission into the message's tags and variables (see below); other X-Wemail-* and X-SES-* names are reserved.

Control headers

The relay parses these optional headers from the MIME message. Header names are case-insensitive.

ParameterTypeRequiredDescription
`X-Wemail-Tag`stringOptionalComma-separated message tags, for example order-confirmation, premium. Tags are stored with the message, can be used to filter events and analytics, and are echoed back in every webhook payload for the message.
`X-Wemail-Variables`JSON objectOptionalVariables to store with the message, for example {"customer_name":"John","order_id":"12345"}. Echoed back in webhook payloads so your handlers can correlate without a lookup — and substituted into the template when X-Wemail-Template is set. Invalid JSON is ignored on plain sends but rejects a template send (550).
`X-Wemail-Template`stringOptionalRender a stored template instead of the MIME body — pass the template’s id (tpl_…, shown on the template in the Console and returned by the Templates API). {{variable}} placeholders are filled from X-Wemail-Variables. An unknown template id rejects the send with a permanent 550 — the raw body is never delivered in its place.
`X-Wemail-Webhook`string (URL)OptionalPer-message webhook URL. Every event for this message is also POSTed there, in addition to your configured webhook endpoints — same as the API webhook_url field. Malformed values are rejected with 550. The header is consumed at submission and never appears on the delivered email.

Send with a stored template

Design once in the Console (or the Templates API), then trigger from anything that can speak SMTP — no REST call needed. Pass the template id and its variables as headers; the MIME body acts only as a fallback for parts the template does not define (for example a text alternative).

textmessage.eml
From: "Acme Support" <you@yourdomain.com>
To: alex@example.com
Subject: (replaced by the template subject)
X-Wemail-Template: tpl_9f2c41d8a7b3e650
X-Wemail-Variables: {"name":"Stelios","order_id":"2940"}

This body is used only where the template has no content.

SMTP acceptance versus delivery

The SMTP response is synchronous and only tells you whether wemail accepted the message for processing. A successful 250 response means the MIME message was saved and queued; it does not mean the recipient mail server accepted it. Delivery outcomes arrive later as events through webhooks or GET /v3/events.

ParameterTypeRequiredDescription
`250` successacceptedOptionalMessage accepted by wemail, persisted with status queued, and placed on the delivery queue. The submission timeline (parse, sender check, quota, enqueue) is visible in the Activity drawer.
`535`authentication failedOptionalThe AUTH stage rejected the credentials: unknown, revoked or malformed API key, or a read_only key (which cannot send). Fix the credentials — retrying the same ones cannot succeed, and repeated failures from one IP are throttled.
`550`permanently rejectedOptionalThe message itself can never be delivered as submitted: the From domain is not registered or not verified for the account, the From address has no domain, X-Wemail-Variables is not valid JSON on a template send, X-Wemail-Template names an unknown template id, or the submission failed validation. Correct the message before sending again — a retry of the identical message is rejected again.
`552`message too largeOptionalThe message exceeds the 20 MB wire limit — precisely 20,971,520 bytes (20 MiB), the value advertised in the EHLO SIZE extension. A MAIL FROM declaring a larger SIZE= is rejected before DATA; undeclared oversized messages are rejected mid-stream. The limit applies to the encoded MIME message (headers + bodies + base64 attachments). For larger files, host them via POST /v3/uploads and link them instead.
`452`quota exhaustedOptionalThe account’s sending quota (plan allowance or test-key allowance) is used up. Temporary: retry after the quota resets, or upgrade the plan. Standard SMTP clients treat 4xx as retryable and will re-attempt automatically.
`451`server busyOptionalThe relay is at its concurrent-processing limit. Temporary — retry shortly; well-behaved SMTP clients back off and retry on their own.
`450`transient failureOptionalAn unexpected processing failure (parse or storage error). Temporary — safe to retry. All rejected submissions (450/452/550/552) appear in Activity as rejected with the failing step and reason, deduplicated over 15 minutes so aggressive client retries don’t flood the grid.
`554`connection errorOptionalA protocol-level failure on the connection itself (for example a TLS or command-sequence error) rather than a verdict on the message. Reconnect and resubmit.

Code examples

cURL
# curl speaks SMTP too (or use swaks / your mail library).
# message.eml starts with your headers — a From display name is honored:
#   From: "Acme Support" <you@yourdomain.com>
#   To: alex@example.com
#   Subject: Welcome to Acme
curl --ssl-reqd --url 'smtp://smtp.wemail.io:587' \
  --user 'you@yourdomain.com:afn_live_pK7…b9aF' \
  --mail-from 'you@yourdomain.com' \
  --mail-rcpt 'alex@example.com' \
  --upload-file message.eml
Node.js
import nodemailer from "nodemailer";

const transport = nodemailer.createTransport({
  host: "smtp.wemail.io",
  port: 587,               // 465 for TLS, 2525 fallback
  auth: {
    user: "you@yourdomain.com",          // a verified sender domain
    pass: process.env.WEMAIL_API_KEY,     // full_access or send_only key
  },
});

await transport.sendMail({
  from: '"Acme Support" <you@yourdomain.com>',  // display name shown to recipients
  to: "alex@example.com",
  cc: "ops@yourdomain.com",             // cc/bcc delivered + tracked per recipient
  replyTo: "support@yourdomain.com",
  subject: "Welcome to Acme",
  html: "<h1>Hi Alex</h1>",
  text: "Hi Alex",                       // plain-text alternative
  attachments: [{ filename: "invoice.pdf", path: "./invoice.pdf" }],
  headers: {
    "X-Wemail-Tag": "welcome, onboarding",                    // tags — filter events/analytics
    "X-Wemail-Variables": JSON.stringify({ order_id: "42" }),  // echoed in webhooks
    "X-Reference-Id": "order-42",       // any X-* header — echoed in webhooks
  },
});
Python
import os, smtplib
from email.mime.text import MIMEText

msg = MIMEText("<h1>Hi Alex</h1>", "html")
msg["From"] = "Acme Support <you@yourdomain.com>"  # display name shown to recipients
msg["To"] = "alex@example.com"
msg["Subject"] = "Welcome to Acme"
msg["X-Wemail-Tag"] = "welcome, onboarding"      # tags — filter events/analytics
msg["X-Wemail-Variables"] = '{"order_id": "42"}'  # echoed in webhooks
msg["X-Reference-Id"] = "order-42"                # any X-* header — echoed in webhooks

with smtplib.SMTP("smtp.wemail.io", 587) as s:
    s.starttls()
    s.login("you@yourdomain.com", os.environ["WEMAIL_API_KEY"])
    s.send_message(msg)
PHP
<?php
// PHPMailer over the wemail SMTP relay
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.wemail.io';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->Username = 'you@yourdomain.com';
$mail->Password = getenv('WEMAIL_API_KEY');
$mail->setFrom('you@yourdomain.com', 'Acme Support');  // display name shown to recipients
$mail->addAddress('alex@example.com');
$mail->Subject = 'Welcome to Acme';
$mail->isHTML(true);
$mail->Body = '<h1>Hi Alex</h1>';
$mail->send();
Ruby
require "mail"
Mail.defaults do
  delivery_method :smtp,
    address: "smtp.wemail.io", port: 587,
    user_name: "you@yourdomain.com",
    password: ENV["WEMAIL_API_KEY"],
    enable_starttls_auto: true
end

Mail.deliver do
  from    "Acme Support <you@yourdomain.com>"
  to      "alex@example.com"
  subject "Welcome to Acme"
  html_part { content_type "text/html"; body "<h1>Hi Alex</h1>" }
end
Go
import "net/smtp"

auth := smtp.PlainAuth("", "you@yourdomain.com", os.Getenv("WEMAIL_API_KEY"), "smtp.wemail.io")
msg := []byte("From: \"Acme Support\" <you@yourdomain.com>\r\n" +
    "To: alex@example.com\r\n" +
    "Subject: Welcome to Acme\r\n" +
    "Content-Type: text/html\r\n\r\n" +
    "<h1>Hi Alex</h1>\r\n")
smtp.SendMail("smtp.wemail.io:587", auth, "you@yourdomain.com", []string{"alex@example.com"}, msg)
Java
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.wemail.io");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");

Session session = Session.getInstance(props, new Authenticator() {
  protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication("you@yourdomain.com", System.getenv("WEMAIL_API_KEY"));
  }
});
MimeMessage m = new MimeMessage(session);
m.setFrom(new InternetAddress("you@yourdomain.com", "Acme Support")); // display name shown to recipients
m.addRecipients(Message.RecipientType.TO, "alex@example.com");
m.setSubject("Welcome to Acme");
m.setContent("<h1>Hi Alex</h1>", "text/html");
Transport.send(m);
.NET
using System.Net;
using System.Net.Mail;

var client = new SmtpClient("smtp.wemail.io", 587) {
  EnableSsl = true,
  Credentials = new NetworkCredential(
    "you@yourdomain.com",
    Environment.GetEnvironmentVariable("WEMAIL_API_KEY")),
};
var mail = new MailMessage(
    new MailAddress("you@yourdomain.com", "Acme Support"), // display name shown to recipients
    new MailAddress("alex@example.com")) {
  Subject = "Welcome to Acme",
  Body = "<h1>Hi Alex</h1>",
  IsBodyHtml = true,
};
client.Send(mail);