=============== 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/ HTTP/1.1 upload the rfc822 message:: PUT /transactions//body HTTP/1.1 additionally, if you enable message parsing in the output chain:: PUT /transactions//message_builder HTTP/1.1 PUT /transactions/blob/ HTTP/1.1 for each blob in the message builder spec json