Koukan REST API

Sending

Koukan provides a rich http/json rest api to send and receive email.

The api has 1 type of resource: the transaction, a request to send one message to one recipient. The transaction object contains fields corresponding to smtp parameters.

Transaction fields

mail_from: Mailbox

mail_response: Response

rcpt_to: Array[Mailbox]

rcpt_response: Array[Response]

first-class rest api users will only use 1 rcpt_to/rcpt_response. (only the smtp gateway uses multiple)

data_response: Response

Transaction body: field

In creation requests:

  • inline rfc822 {"inline": "Subject: hello\r\n\r\n"}

  • a request to reuse a blob {"reuse_uri": "/transactions/xyz/body"}

  • a message builder specification message_builder.json

    {"message_builder": {
       "headers": [["subject", "hello"]],
           "text_body": [{
             "content_type": "text/plain",
             "content": {"inline": "hello, world!"}}],
    }}
    

Returned from GET

  • blob status:

    {"blob_status": {"finalized": true}}
    
  • blob url to PUT:

    {"blob_status": {"uri": "http://router.local/transactions/xyz/body"}}
    
  • either of the above for each blob in MessageBuilder Spec:

    {"message_builder": {"blob_status": {
     "my_plain_body": {"finalized": true},
     "my_html_body": {"uri": "http://router.local/transactions/xyz/blob/my_html_body"}}}}
    

HostPort

host: string

port: int

Mailbox

m: rfc5321 mailbox without <>

e: Array[EsmtpParam]

EsmtpParam

keyword: string

value: string (optional)

Response

code: int

message: string

Simplest case

If the message only contains a moderate amount of plain text, we can send a message with a single POST:

POST /senders/submission/transactions HTTP/1.1
Content-type: application/json
{"mail_from": {"m": "alice@example.com"},
 "rcpt_to": {"m": "bob@example.com"},
 "body": {"message_builder": {
     "headers": [["subject", "hello"]],
     "text_body": [{
       "content_type": "text/plain",
       "content": {"inline": "hello, world!"}}],
}}}

201 created
Location: /transactions/xyz
Content-type: application/json

{"mail_from": {}, "rcpt_to": {}, "body": {}}

Koukan transactions are write-once; the {} is a placeholder indicating the field is populated. Transactions are long-running operations (lro) that track the status of the message delivery. With a request-timeout header, Koukan will do a hanging GET:

GET /transactions/xyz  HTTP/1.1
request-timeout: 10

(some time elapses)
200 ok
Content-type: application/json

{"mail_from": {}, "rcpt_to": {}, "body": {},
 "mail_response": {"code": 250 },
 "rcpt_response": {"code": 250 },
 "data_response": {"code": 250 },
 "attempt_count": 1,
 "final_attempt_reason": "upstream response success"
}

Response fields are per the most recent attempt.

final_attempt_reason is a human-readable string that if non-null indicates that Koukan is done with this transaction. Completed transactions are garbage-collected after a configured interval (e.g. 1h) from the time they were completed.

Large/Binary Attachments

If the body is not suitable to inline in JSON, specify an id within the message_builder spec. Koukan returns a url to PUT the blob to:

POST /senders/submission/transactions  HTTP/1.1
Content-type: application/json

{"mail_from": {"m": "alice@example.com"},
 "rcpt_to": {"m": "bob@example.com"},
 "body": {"message_builder": {
     "headers": [["subject", "hello"]],
     "text_body": [{
       "content_type": "text/html",
       "content": {"create_id": "my_body"}}]
}}}

201 created
Location: /transactions/xyz

{"body": {"message_builder": { "blob_status": {
 "my_body": { "uri": "http://router.local/transactions/xyz/blob/my_body"}}}}}

PUT /transactions/xyz/blob/my_body  HTTP/1.1
content-length: 12345678

200 ok

GET /transactions/xyz  HTTP/1.1

200 ok
{"body": {"message_builder": { "blob_status": {
 "my_body": { "finalized": true}}}}}

Blob Reuse

A transaction can reuse an attachment from a previous transaction. If the reuse succeeded, this will be reflected in blob_status. Note: the id returned in blob_status will be the same as from the reused blob.

POST /senders/submission/transactions HTTP/1.1
Content-type: application/json

{"mail_from": {"m": "alice@example.com"},
 "rcpt_to": {"m": "bob@example.com"},
 "body": {"message_builder": {
     "headers": [["subject", "hello"]],
     "text_body": [{
       "content_type": "text/plain",
       "content": { "reuse_uri": "/transactions/xyz/blob/my_body"}
     }]
}}}

201 created
Location: /transactions/xyz

{"mail_from": {},
 "rcpt_to": {},
 "body": {"message_builder": { "blob_status": {
 "my_body": {"finalized": true}}}}}

Pre-serialized rfc822/mime message

If you already have a serialized rfc822/mime message you want to send, create the transaction without the body field. Similar to above, Koukan will return a URL to PUT the blob to:

POST /senders/submission/transactions HTTP/1.1
Content-type: application/json
{"mail_from": {"m": "alice@example.com"},
 "rcpt_to": {"m": "bob@example.com"}}}}

201 created
Location: /transactions/xyz
Content-type: application/json

{"mail_from": {}, "rcpt_to": {}, "body": {"blob_status": {
 "uri": "http://router.local/transactions/xyz/body"}}}

PUT /transactions/xyz/body HTTP/1.1

200 ok

GET /transactions/xyz HTTP/1.1

200 ok

{"mail_from": {}, "rcpt_to": {}, "body": {"blob_status": {
 "finalized": "true"}}}

Body reuse

Similarly, you can reuse an rfc822 body from a previous transaction:

POST /senders/submission/transactions HTTP/1.1
Content-type: application/json

{"mail_from": {"m": "alice@example.com"},
 "rcpt_to": {"m": "bob@example.com"},
 "body": {"reuse_uri": "/transactions/xyz/body"}
}

GET /transactions/xyz HTTP/1.1

200 ok

{"mail_from": {}, "rcpt_to": {}, "body": {"blob_status": {
 "finalized": "true"}}}

Cancellation

To cancel a transaction, simply:

POST /transactions/123/cancel HTTP/1.1

with an empty entity. This is a no-op if the transaction already has final_attempt_reason. This will manifest as cancelled: true in the transaction json. Note: cancellation will not abort an inflight OutputHandler that is waiting on the upstream but will abort on the next iteration of OutputHandler.handle().

Receiving

Receiving is a little more complicated due to the need to be compatible with gatewaying from interactive (non-pipelined) SMTP.

cf examples/receiver

Your application must expose the following routes/endpoints:

POST /senders/router/transactions HTTP/1.1

201 created
Location: /transactions/123

create a new transaction and return the path in location:

GET /transactions/<tx id> HTTP/1.1

upload the rfc822 message:

PUT /transactions/<tx id>/body HTTP/1.1

additionally, if you enable message parsing in the output chain:

PUT /transactions/<tx id>/message_builder HTTP/1.1
PUT /transactions<tx id>/blob/<blob id> HTTP/1.1

for each blob in the message builder spec json