The Contact Inbox

8 vues Markdown

The nine endpoints that read, move and answer the contact form inbox.

Overview

Messages from the contact form behave like an inbox: unread, read, replied, spam and trash. These nine endpoints read that inbox, move messages between folders and carry out two real actions.

The folder is not a stored field; it is computed from the record's status and its read mark. Moving a message to read leaves the status alone and flips the mark instead.

Two endpoints reach outside the installation: replying sends the visitor a real e-mail, and converting opens a real support ticket. Neither can be undone.

Reference

Listing the Messages

get/api/v1/admin/website/messages
Website/GetMessages admin

Returns the contact form messages in a folder.

Query 4
folderstringWhich folder: unread, read, replied, spam, trash. The unread ones by default.
searchstringSearches the messages.
pageintWhich page.
limitintRecords per page. A hundred at most.
Response fields data[] — 16 + meta — 5
idintThe message id.
full_namestringThe sender name.
emailstringThe sender e-mail.
phonestringThe sender phone.
messagestringThe message text.
ipstringThe address it came from.
langstringThe language the form was filled in.
statusstringThe record's raw status. Normal, replied, spam or trash.
folderstringThe computed folder: unread, read, replied, spam, trash. Derived from the status and the read mark.
unreadintThe read mark. The logic is inverted: zero means unread and one means read.
admin_messagestringThe text of the reply sent.
replied_byintThe administrator who replied.
replied_atstring | nullWhen the reply went out.
converted_to_ticket_idintThe ticket it became. Zero means it has not been converted.
read_byobjectWho read it and when.
created_atstring | nullWhen it was submitted.
totalintHow many the folder holds. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
folder stringThe folder listed.
next_pageintThe next page. Zero means you are on the last one.
Errors 2
invalid_folder422The folder is not recognised.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/website/messages?folder=unread' \
  -H "Authorization: Bearer $API_KEY"
const url = new URL('https://panel.example.com/api/v1/admin/website/messages');
url.searchParams.set('folder', 'unread');

const res  = await fetch(url, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages?' . http_build_query(['folder' => 'unread']));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The read mark is INVERTED: zero means unread and one means read.
$msgs   = Api::Website()->GetMessages([], ['folder' => 'unread'])['data'];
$isRead = $msgs[0]['unread'] === 1;

Reading One Message

get/api/v1/admin/website/messages/{id}
Website/GetMessage admin

Returns a single message.

Response fields data — 16
idintThe message id.
full_namestringThe sender name.
emailstringThe sender e-mail.
phonestringThe sender phone.
messagestringThe message text.
ipstringThe address it came from.
langstringThe language the form was filled in.
statusstringThe record's raw status. Normal, replied, spam or trash.
folderstringThe computed folder: unread, read, replied, spam, trash. Derived from the status and the read mark.
unreadintThe read mark. The logic is inverted: zero means unread and one means read.
admin_messagestringThe text of the reply sent.
replied_byintThe administrator who replied.
replied_atstring | nullWhen the reply went out.
converted_to_ticket_idintThe ticket it became. Zero means it has not been converted.
read_byobjectWho read it and when.
created_atstring | nullWhen it was submitted.
Errors 2
message_not_found404No such message.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/website/messages/20' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Reading does NOT mark it read; the mark is set by a call of its own.
$msg = Api::Website()->GetMessage(['id' => $id])['data'];

Moving Messages in Bulk

post/api/v1/admin/website/messages/bulk-move
Website/BulkMoveMessages admin can block the sender

Moves several messages into another folder in one call.

Body 4
idsarrayreqThe message ids to move.
folderstringreqThe folder to move them to: unread, read, replied, spam, trash.
block_emailsboolAdds the sender's e-mail and phone to the banned list. It works on the spam folder alone.
report_spamboolBlocks the sender's address. It works on the spam folder alone.
Response fields data — 2
movedintHow many were moved.
folderstringThe folder they went to.
Errors 3
ids_required422No message was given.
invalid_folder422The folder is not recognised.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/messages/bulk-move' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ids":[20,21],"folder":"trash"}'
const res = await fetch('https://panel.example.com/api/v1/admin/website/messages/bulk-move', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ids: [20, 21], folder: 'trash' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/bulk-move');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['ids' => [20, 21], 'folder' => 'trash']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Two options reach PAST THE MESSAGE: the sender is banned and their address blocked.
Api::Website()->BulkMoveMessages([
    'ids'          => $ids,
    'folder'       => 'spam',
    'block_emails' => true,
    'report_spam'  => true,
]);

Emptying a Folder

post/api/v1/admin/website/messages/empty-folder
Website/EmptyMessageFolder admin a permanent delete

Empties the spam or the trash folder outright.

Body 1
folderstringreqThe folder to empty. Only the spam and trash folders can be emptied.
Response fields data — 1
emptiedstringThe folder emptied.
Errors 2
invalid_folder422That folder cannot be emptied.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/messages/empty-folder' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"folder":"trash"}'
const res = await fetch('https://panel.example.com/api/v1/admin/website/messages/empty-folder', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ folder: 'trash' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/empty-folder');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['folder' => 'trash']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// It does NOT say how many went and cannot be undone; list the folder and count first.
$before = Api::Website()->GetMessages([], ['folder' => 'trash'])['meta']['total'];
Api::Website()->EmptyMessageFolder(['folder' => 'trash']);

Moving One Message

put/api/v1/admin/website/messages/{id}/folder
Website/SetMessageFolder admin

Moves one message into another folder.

Body 3
folderstringreqThe folder to move it to: unread, read, replied, spam, trash.
block_emailsboolAdds the sender to the banned list.
report_spamboolBlocks the sender's address.
Response fields data — 16
dataobjectThe message as it now stands. Same shape as the detail endpoint.
Errors 3
message_not_found404No such message.
invalid_folder422The folder is not recognised.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/website/messages/20/folder' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"folder":"read"}'
const res = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}/folder`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ folder: 'read' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id . '/folder');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['folder' => 'read']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The read and unread folders leave the STATUS alone and flip the read mark instead.
Api::Website()->SetMessageFolder(['id' => $id, 'folder' => 'read']);

Marking a Message Read

post/api/v1/admin/website/messages/{id}/read
Website/ReadMessage admin

Marks a message read and records who read it.

Body
No body is needed. The message comes from the path; send an empty body.
Response fields data — 16
dataobjectThe message as it now stands. The administrator joins the readers map.
Errors 2
message_not_found404No such message.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/messages/20/read' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}/read`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id . '/read');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The readers map keeps EACH administrator apart, while the mark is one for the panel.
Api::Website()->ReadMessage(['id' => $id]);

Replying to a Message

post/api/v1/admin/website/messages/{id}/reply
Website/ReplyMessage admin a real e-mail goes out

Sends the visitor an e-mail reply and moves the message to replied.

Body 3
messagestringreqThe reply text to send.
save_as_templateintKeeps the reply for later use.
template_namestringThe name to keep it under. Needed when you ask for it to be kept.
Response fields data — 16
dataobjectThe message as it now stands. Its status becomes replied.
Errors 5
message_not_found404No such message.
message_required422The reply text is empty.
template_name_required422Keeping was asked for with no name given.
reply_failed422The e-mail could not be sent.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/messages/20/reply' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"message":"Ilginiz icin tesekkurler, en kisa surede donecegiz."}'
const res = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}/reply`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: "Thanks for reaching out - we'll get back to you shortly.",
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id . '/reply');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'message' => 'Thanks for reaching out.',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This call MAILS THE VISITOR and cannot be undone; check the text before sending.
Api::Website()->ReplyMessage([
    'id'      => $id,
    'message' => $text,
]);

Turning It into a Ticket

post/api/v1/admin/website/messages/{id}/convert-to-ticket
Website/ConvertMessageToTicket admin opens a real ticket

Opens a support ticket from a message and ties the two together.

Body 4
departmentintreqThe department the ticket goes to.
priorityintThe ticket priority.
staffintThe administrator to assign the ticket to.
notesstringAn internal note for the ticket.
Response fields data — 2
ticket_idintThe ticket opened.
message_idintThe message it came from.
Errors 5
message_not_found404No such message.
department_required422No department was given.
blocked_by_gate422A hook refused the conversion.
ticket_failed500The ticket could not be opened.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/website/messages/20/convert-to-ticket' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"department":1,"priority":2}'
const res = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}/convert-to-ticket`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ department: 1, priority: 2 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id . '/convert-to-ticket');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['department' => 1, 'priority' => 2]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The message CARRIES which ticket it became; read that before converting again.
$msg = Api::Website()->GetMessage(['id' => $id])['data'];
if ($msg['converted_to_ticket_id'] === 0)
    Api::Website()->ConvertMessageToTicket(['id' => $id, 'department' => 1]);

Deleting a Message

delete/api/v1/admin/website/messages/{id}
Website/DeleteMessage admin

Removes one message for good.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the message removed.
Errors 2
message_not_found404No such message.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/website/messages/20' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/website/messages/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/website/messages/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Move it to TRASH rather than delete: the record stays and can come back if needed.
Api::Website()->SetMessageFolder(['id' => $id, 'folder' => 'trash']);

Pitfalls

The read mark runs backwards

In the read field zero means unread and one means read. The name suggests the opposite, so the logic here often gets read backwards and the inbox count comes out wrong. Test against one rather than trusting the value as a boolean.

The folder is computed rather than stored

What the record holds is the status and the read mark, and the folder is derived from those two. Moving a message to read leaves the status alone and flips the mark, while moving it to spam or trash changes the status. When filtering on your side, use the computed field rather than the raw status.

Moving to spam can block the sender

Two options on the move calls reach far past the message: one adds the sender's e-mail and phone to the banned list, the other blocks their address. Both apply across the installation and keep that person from using the form again. Leave them off when doing a bulk clean-up.

The reply really goes out

The reply endpoint saves no draft; it sends the visitor a real e-mail, and a sent message cannot be pulled back. The convert endpoint likewise opens a real ticket. When trying these two from a script, use a test record that reaches your own address.

Emptying a folder is permanent and gives no count

The empty call works on the spam and trash folders alone, yet there it removes everything for good and does not say how many went. The response names only the folder emptied. When you need a record, list the folder before emptying it.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.