TeneoSMTP RELAY
Developers

Submission API

Send through the Teneo relay with one HTTPS request instead of an SMTP session. JSON in, JSON out. The same relay login, the same IP addresses and the same delivery log as SMTP, so nothing changes in your console.

Every message must carry mailclass. Its value is the name of your relay account: the part between mailclass- and @ in your login. For the login mailclass-orders@tlsrelay.teneo.be it is "mailclass": "orders". A message without it is accepted by the API and then discarded by the relay: nothing is delivered and nobody is told. Over SMTP the login decides the mail class; over HTTPS you have to say it.

Endpoint

URLhttps://tlsrelay.teneo.be/api/v1/send.json MethodPOST HeaderContent-Type: application/json CompressionOptional. Content-Encoding: gzip or deflate on the request body. CredentialsYour relay account login and its password, in the JSON body as username and password. Create and rotate them on the Relay accounts page of the console.

Always use https. The body carries your password.

One message

{
  "username": "mailclass-orders@tlsrelay.teneo.be",
  "password": "YOUR_RELAY_PASSWORD",
  "message": {
    "mailclass": "orders",
    "from_email": "orders@yourdomain.be",
    "from_name": "Your Company",
    "to": [{ "email": "jan@example.be", "name": "Jan Peeters" }],
    "subject": "Your order 4821 has shipped",
    "html": "<p>Your order is on its way.</p>",
    "text": "Your order is on its way.",
    "idempotence_key": "order-4821-shipped"
  }
}
curl -X POST https://tlsrelay.teneo.be/api/v1/send.json \
  -H "Content-Type: application/json" \
  --data-binary @message.json

Answer when the relay accepted it:

{"success":1,"message_id":"<3f9c…@yourdomain.be>","result_reason":"success_immediate"}

Answer when it did not:

{"success":0,"error":"incorrect username/password","result_reason":"request_error"}

Fields

Top level of the request body:

FieldRequiredMeaning
usernameyesYour relay account login, mailclass-<name>@tlsrelay.teneo.be.
passwordyesThat account's password.
messageone of the twoOne message object (below).
messagesone of the twoAn array of up to 500 message objects, answered one by one (see Batches).
max_request_timenoSeconds the relay may spend accepting the messages before it answers, 1 to 60, default 31. Transfer time does not count. Messages not reached in time come back as not attempted.

Message object:

FieldRequiredMeaning
mailclassyes, alwaysThe name of your relay account, the part between mailclass- and @ in the login. Without it the message is discarded.
from_emailyesFrom address. The domain must be one of your authenticated domains in the console, or the mail will not be signed.
from_namenoFrom display name.
toyesArray of recipients, each {"email": "…", "name": "…"}; name is optional.
ccnoSame shape as to.
bccnoArray of addresses. Delivered to, never shown in the headers.
envelope_recipientsnoArray of addresses that overrides who actually receives the message. When absent, to, cc and bcc are used.
subjectyesSubject line.
htmlhtml or textHTML body. Both may be given; the relay builds the multipart message.
texthtml or textPlain-text body.
amp_htmlnoAMP for Email body. Never on its own.
attachmentsnoArray of attachment objects (below).
headersnoObject of extra headers, name to raw value. Message-ID and Date are added when missing; a Message-ID you supply must include its angle brackets.
return_pathnoBounce address. Leave it out: the relay sets its own so bounces reach your delivery log and the feedback loops work.
message_id_domainnoDomain used in a generated Message-ID. Use your sending domain.
idempotence_keynoAny string unique to this message, for example your order number plus the event. A second request with the same key within 4 hours is not sent again and answers "duplicate":1. Use it whenever you retry.
idnoYour own reference, echoed in batch answers so you can match them. Not the Message-ID header.
mimenoA complete, already built RFC 5322 message instead of the fields above (see Raw messages).

Attachment object:

FieldRequiredMeaning
filenameyesFile name shown to the recipient.
content_typeyesMIME type, for example application/pdf.
content_base64one of the twoThe file, base64 encoded. The usual choice.
contentone of the twoThe file as a raw string; the relay encodes it. Text files only.
content_dispositionnoattachment for a file to download, inline for an image referenced from the HTML. Default is inline, so set attachment for documents.
content_idnoFor inline images: the id referenced as cid: in your HTML, in the form <logo@yourdomain.be>.

Batches

Put up to 500 message objects in messages instead of message. Each one needs its own mailclass. The answer lists a result per message, in order, with your id echoed:

{
  "success": 1,
  "messages": [
    {"success": 1, "message_id": "<…>", "attempted": 1, "id": "4821", "result_reason": "success_immediate"},
    {"success": 0, "error": "…",         "attempted": 1, "id": "4822", "result_reason": "message_validation_error"},
    {"success": 0, "error": "not attempting because previous messages have taken too long", "attempted": 0, "id": "4823", "result_reason": "skipped_batch_timeout"}
  ]
}

A message with "attempted": 0 was never tried: send it again, preferably in a smaller batch. A good batch size is what the relay accepts in about ten seconds; start with 100 and adjust. Reuse the TCP connection between calls.

Result reasons

result_reasonMeaningWhat to do
success_immediateAccepted and queued.Nothing. Watch the delivery log.
success_after_throttledAccepted after a short wait because the relay was busy.Nothing. Smaller batches if it keeps happening.
duplicateSame idempotence_key seen in the last 4 hours; not sent again.Nothing, it was already sent.
message_validation_errorThis message is wrong: a missing field, a bad address, a broken attachment.Fix it. Retrying unchanged fails again.
request_errorThe request as a whole is wrong: credentials, malformed JSON, no body.Fix it. Retrying unchanged fails again.
aborted_after_throttledThe relay was too busy within max_request_time.Retry with the same idempotence_key.
internal_errorSomething broke on our side.Retry with the same key. If it persists, open a ticket.
skipped_batch_internal_errorNot tried because an earlier message in the batch hit an internal error.Retry in a new batch.
skipped_batch_timeoutNot tried because the batch took too long.Retry in a smaller batch.

Raw messages

If your application already builds complete messages, send them as they are. mime replaces the content fields and envelope_recipients becomes required. mailclass stays required.

{
  "username": "mailclass-orders@tlsrelay.teneo.be",
  "password": "YOUR_RELAY_PASSWORD",
  "message": {
    "mailclass": "orders",
    "envelope_recipients": ["jan@example.be"],
    "mime": "From: orders@yourdomain.be\r\nTo: jan@example.be\r\nSubject: Your order 4821\r\nMessage-ID: <4821@yourdomain.be>\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nYour order is on its way.\r\n"
  }
}

With mime these fields are refused: html, text, amp_html, subject, to, cc, bcc, from_email, from_name, attachments, headers, message_id_domain. The relay does not inspect the content beyond checking it is not empty; include your own Message-ID if you want it back in the answer.

SMTP or HTTPS?

Both reach the same place. SMTP on port 587 with STARTTLS suits anything that already has a mail library or a mail server behind it. The Submission API suits applications that speak HTTP anyway, that want an answer per message, or that send in bursts: a batch of 500 is one request. You can use both with the same relay account at the same time.

Questions? Open a ticket on the Support page of the console; an engineer answers.