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.
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
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:
| Field | Required | Meaning |
|---|---|---|
| username | yes | Your relay account login, mailclass-<name>@tlsrelay.teneo.be. |
| password | yes | That account's password. |
| message | one of the two | One message object (below). |
| messages | one of the two | An array of up to 500 message objects, answered one by one (see Batches). |
| max_request_time | no | Seconds 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:
| Field | Required | Meaning |
|---|---|---|
| mailclass | yes, always | The name of your relay account, the part between mailclass- and @ in the login. Without it the message is discarded. |
| from_email | yes | From address. The domain must be one of your authenticated domains in the console, or the mail will not be signed. |
| from_name | no | From display name. |
| to | yes | Array of recipients, each {"email": "…", "name": "…"}; name is optional. |
| cc | no | Same shape as to. |
| bcc | no | Array of addresses. Delivered to, never shown in the headers. |
| envelope_recipients | no | Array of addresses that overrides who actually receives the message. When absent, to, cc and bcc are used. |
| subject | yes | Subject line. |
| html | html or text | HTML body. Both may be given; the relay builds the multipart message. |
| text | html or text | Plain-text body. |
| amp_html | no | AMP for Email body. Never on its own. |
| attachments | no | Array of attachment objects (below). |
| headers | no | Object 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_path | no | Bounce address. Leave it out: the relay sets its own so bounces reach your delivery log and the feedback loops work. |
| message_id_domain | no | Domain used in a generated Message-ID. Use your sending domain. |
| idempotence_key | no | Any 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. |
| id | no | Your own reference, echoed in batch answers so you can match them. Not the Message-ID header. |
| mime | no | A complete, already built RFC 5322 message instead of the fields above (see Raw messages). |
Attachment object:
| Field | Required | Meaning |
|---|---|---|
| filename | yes | File name shown to the recipient. |
| content_type | yes | MIME type, for example application/pdf. |
| content_base64 | one of the two | The file, base64 encoded. The usual choice. |
| content | one of the two | The file as a raw string; the relay encodes it. Text files only. |
| content_disposition | no | attachment for a file to download, inline for an image referenced from the HTML. Default is inline, so set attachment for documents. |
| content_id | no | For 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_reason | Meaning | What to do |
|---|---|---|
| success_immediate | Accepted and queued. | Nothing. Watch the delivery log. |
| success_after_throttled | Accepted after a short wait because the relay was busy. | Nothing. Smaller batches if it keeps happening. |
| duplicate | Same idempotence_key seen in the last 4 hours; not sent again. | Nothing, it was already sent. |
| message_validation_error | This message is wrong: a missing field, a bad address, a broken attachment. | Fix it. Retrying unchanged fails again. |
| request_error | The request as a whole is wrong: credentials, malformed JSON, no body. | Fix it. Retrying unchanged fails again. |
| aborted_after_throttled | The relay was too busy within max_request_time. | Retry with the same idempotence_key. |
| internal_error | Something broke on our side. | Retry with the same key. If it persists, open a ticket. |
| skipped_batch_internal_error | Not tried because an earlier message in the batch hit an internal error. | Retry in a new batch. |
| skipped_batch_timeout | Not 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.