Drafts

A draft is an email written in Carom and not yet sent: a new message, or a reply to a thread. It records the mailbox it will go out from, its recipients, and its body. Use this resource to prepare messages for a person to review and send.

Drafts are private to their author. Your key sees and edits only the drafts of your key’s user. Sending happens in the Carom app; the API can write drafts but not send them. After sending, status changes as delivery progresses, and the draft stays readable here as a record of what went out.

#The draft object

Returned by every draft endpoint.

Attributes

  • idstring · uuid

    Unique identifier for the draft.

  • statusstring

    The draft’s delivery status. Only draft and failed drafts can be edited or deleted. queued: accepted for sending. sending: being handed to the provider. sent: the provider accepted it. failed: delivery failed, and last_error says why; editing it returns it to draft. cancelled: its mailbox was deleted before delivery began. delivery_unknown: its mailbox was deleted during delivery, so it may or may not have gone out.

    draftqueuedsendingsentfailedcancelleddelivery_unknown
  • draft_typestring · nullable

    A new message, or a reply in an existing thread.

    composereply
  • mailbox_idstring · uuid · nullable

    The mailbox the draft will be sent from. Null after that mailbox has been deleted.

  • thread_idstring · uuid · nullable

    The thread a reply belongs to. Null for compose drafts.

  • to_email_addressesarray of strings
  • cc_email_addressesarray of strings
  • bcc_email_addressesarray of strings
  • subjectstring · nullable
  • bodystring · nullable

    The plain-text body.

  • body_htmlstring · nullable

    The HTML body, as Carom stored it after sanitizing. When it is set, the message is sent with both bodies, and body is the plain-text version.

  • content_statestring · nullable

    An opaque string your client can store with the draft. Carom returns it unchanged; it has no effect on the message sent.

  • last_errorobject · nullable

    Why the most recent delivery attempt failed. Null when no attempt has failed.

    Show 2 child attributesHide child attributes
    • codestring

      A stable code for the failure. delivery_failed is the general case.

      outbound_disabledsend_permission_lostmailbox_not_authorizedrate_limitedprovider_rejecteddelivery_setup_failedlocal_delivery_failedenqueue_faileddelivery_never_starteddelivery_admission_failedmailbox_deletedmailbox_deleted_during_deliverydelivery_failed
    • messagestring

      A sentence describing the failure that is safe to show the user. The provider’s own error isn’t returned.

  • rfc_idstring · nullable

    The Message-ID Carom assigned when delivery began, without angle brackets. Null until then.

  • created_atstring · date-time · nullable
  • updated_atstring · date-time · nullable
  • queued_atstring · date-time · nullable

    When your key’s user sent the draft.

  • sending_atstring · date-time · nullable

    When Carom began handing it to the provider.

  • sent_atstring · date-time · nullable

    When the provider accepted it.

  • last_failed_atstring · date-time · nullable
The draft object
{
  "id": "8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31",
  "status": "draft",
  "draft_type": "reply",
  "mailbox_id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f70",
  "thread_id": "7c2e9a14-5b3d-4f8e-9a61-3d0b8e2f4c57",
  "to_email_addresses": ["priya@wildgrove.example"],
  "cc_email_addresses": [],
  "bcc_email_addresses": [],
  "subject": "Re: Q4 renewal terms",
  "body": "Hi Priya, confirmed: the per-site rate stays the same for the 12-month term. Paperwork to follow by the 15th.",
  "body_html": "<p>Hi Priya, confirmed: the per-site rate stays the same for the 12-month term. Paperwork to follow by the 15th.</p>",
  "content_state": null,
  "last_error": null,
  "rfc_id": null,
  "created_at": "2026-09-05T14:08:51.133Z",
  "updated_at": "2026-09-05T14:12:03.538Z",
  "queued_at": null,
  "sending_at": null,
  "sent_at": null,
  "last_failed_at": null
}
get/drafts Read key

#List drafts

Returns every draft your key’s user has written, in any status, most recently updated first. Pass thread_id to get only the drafts for one thread. Returns every draft in one response; the list isn’t paginated.

Query parameters

  • thread_idstring · uuid

    Only drafts replying to this thread.

Returns

Errors

  • 400invalid_requestthread_id is present but empty.
get/drafts
curl "https://api.carom.io/drafts?thread_id=7c2e9a14-5b3d-4f8e-9a61-3d0b8e2f4c57" \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "drafts": [
    {
      "id": "8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31",
      "status": "draft",
      "draft_type": "reply",
      "subject": "Re: Q4 renewal terms",
      "updated_at": "2026-09-05T14:12:03.538Z",
      …
    },
    {
      "id": "c5e9b2d7-1a4f-4c8e-9b3d-6e0a8f2c5d19",
      "status": "sent",
      "draft_type": "reply",
      "subject": "Re: Q4 renewal terms",
      "updated_at": "2026-09-01T16:20:14.313Z",
      …
    }
  ]
}
post/drafts Write key

#Create a draft

Creates a draft for your key’s user, with status draft. Give a mailbox visible to your key. For a reply, also give the thread; if you leave out the subject, Carom uses the thread’s subject with Re: in front.

Carom doesn’t check recipients or sending permission until the draft is sent, so a draft can be saved incomplete.

Request body application/json

  • draftobjectrequired
    Show 10 child attributesHide child attributes
    • mailbox_idstring · uuidrequired

      The mailbox to send from.

    • draft_typestringdefault compose
      composereply
    • thread_idstring · uuid

      Required when draft_type is reply. Ignored for compose.

    • to_email_addressesarray of strings
    • cc_email_addressesarray of strings
    • bcc_email_addressesarray of strings
    • subjectstring · nullable
    • bodystring · nullable

      Plain text. Sent as the whole message when body_html is empty, and as the plain-text version otherwise.

    • body_htmlstring · nullable

      HTML. Carom sanitizes it before storing: formatting, links, tables, inline styles, and images are kept, and scripts, event handlers, and javascript: URLs are removed.

    • content_statestring · nullable

      An opaque string to store with the draft, such as your editor’s state.

Returns

The new draft object, wrapped in draft, with status 201.

Errors

  • 400invalid_requestA field failed validation, for example a missing mailbox_id or an unknown draft_type. error.fields says which.
  • 400missing_thread_idA reply was requested without a thread_id.
  • 404record_not_foundThe mailbox or thread isn’t visible to your key.
post/drafts
curl https://api.carom.io/drafts \
  -H "Authorization: Bearer $CAROM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "draft": {
      "draft_type": "reply",
      "mailbox_id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f70",
      "thread_id": "7c2e9a14-5b3d-4f8e-9a61-3d0b8e2f4c57",
      "to_email_addresses": ["priya@wildgrove.example"],
      "body": "Hi Priya, confirmed: the per-site rate stays the same for the 12-month term."
    }
  }'
Response201
{
  "draft": {
    "id": "8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31",
    "status": "draft",
    "draft_type": "reply",
    "mailbox_id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f70",
    "thread_id": "7c2e9a14-5b3d-4f8e-9a61-3d0b8e2f4c57",
    "to_email_addresses": ["priya@wildgrove.example"],
    "subject": "Re: Q4 renewal terms",
    "body": "Hi Priya, confirmed: the per-site rate stays the same for the 12-month term.",
    "body_html": null,
    "created_at": "2026-09-05T14:08:51.133Z",
    …
  }
}
get/drafts/{id} Read key

#Retrieve a draft

Returns a single draft. After your key’s user sends it, poll this endpoint to follow status through delivery.

Path parameters

  • idstring · uuidrequired

    The draft’s id.

Returns

The draft object, wrapped in draft.

Errors

  • 404record_not_foundNo draft with that id belongs to your key’s user.
get/drafts/{id}
curl https://api.carom.io/drafts/c5e9b2d7-1a4f-4c8e-9b3d-6e0a8f2c5d19 \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response200
{
  "draft": {
    "id": "c5e9b2d7-1a4f-4c8e-9b3d-6e0a8f2c5d19",
    "status": "sent",
    "draft_type": "reply",
    "subject": "Re: Q4 renewal terms",
    "rfc_id": "4b7e2c9a-6d1f-4a3e-8c5b-0e9d7f2a1c64@carom.io",
    "queued_at": "2026-09-01T16:20:09.128Z",
    "sending_at": "2026-09-01T16:20:11.336Z",
    "sent_at": "2026-09-01T16:20:14.313Z",
    "last_error": null,
    …
  }
}
patch/drafts/{id} Write key

#Update a draft

Changes the fields you send and leaves the rest alone. Recipient lists are replaced whole, not merged. The thread and type can’t be changed after creation.

To change the mailbox the draft is sent from, send a different mailbox_id. Your key’s user must be able to send from the new mailbox now, the same check Carom makes when a draft is sent. Sending the draft’s current mailbox_id changes nothing and isn’t checked.

Only drafts with status draft or failed can be edited. Editing a failed draft sets it back to draft so your key’s user can send it again.

Path parameters

  • idstring · uuidrequired

Request body application/json

  • draftobjectrequired

    Any of mailbox_id, to_email_addresses, cc_email_addresses, bcc_email_addresses, subject, body, body_html, and content_state, as on Create a draft. Send null to clear a text field.

Returns

The updated draft object, wrapped in draft.

Errors

  • 400invalid_requestA field failed validation. error.fields says which.
  • 403permission_deniedYour key’s user isn’t allowed to send from the new mailbox.
  • 404record_not_foundNo draft with that id belongs to your key’s user, or the new mailbox isn’t visible to your key.
  • 409not_editable_statusThe draft’s status isn’t draft or failed.
  • 422mailbox_not_authorizedThe new mailbox has no working connection. Its owner must reconnect it in the Carom app.
  • 422capability_not_grantedThe new mailbox’s owner didn’t grant Carom permission to send from it. The owner must reconnect it in the Carom app.
patch/drafts/{id}
curl -X PATCH https://api.carom.io/drafts/8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31 \
  -H "Authorization: Bearer $CAROM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "draft": {
      "cc_email_addresses": ["owen@harborline.example"],
      "body": "Hi Priya, confirmed: the per-site rate stays the same for the 12-month term. Paperwork to follow by the 15th."
    }
  }'
Response200
{
  "draft": {
    "id": "8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31",
    "status": "draft",
    "cc_email_addresses": ["owen@harborline.example"],
    "body": "Hi Priya, confirmed: the per-site rate stays the same for the 12-month term. Paperwork to follow by the 15th.",
    "updated_at": "2026-09-05T14:12:03.538Z",
    …
  }
}
delete/drafts/{id} Write key

#Delete a draft

Permanently deletes a draft with status draft or failed. Drafts in any other status can’t be deleted.

Path parameters

  • idstring · uuidrequired

Returns

An empty response with status 204.

Errors

  • 404record_not_foundNo draft with that id belongs to your key’s user.
  • 409not_editable_statusThe draft’s status isn’t draft or failed.
delete/drafts/{id}
curl -X DELETE https://api.carom.io/drafts/8a3f1c6e-7d2b-4e9a-b5c8-4f0e2d7a9b31 \
  -H "Authorization: Bearer $CAROM_API_KEY"
Response204
No content