Replies that start a new thread
If a reply sent through the API shows up as a fresh message instead of under the email it answers, look at two fields in the send request. They do different jobs.
replyMessageId identifies the message you're answering. threadId identifies the parent email thread it belongs to. Send both on every reply. Keep the ids from the inbound message when you receive it, so they're on hand when the reply goes out.
There's one more field to set on purpose: emailReplyMode takes reply or reply_all. Pick the one you mean. A reply that should reach everyone on the thread needs reply_all.
Scheduled sends: UTC, in seconds
scheduledTimestamp is a UTC time counted in seconds. Two mistakes follow from that, and both send the email at the wrong moment.
- Sending local time. Convert the contact's or the business's local send time to UTC before you build the timestamp.
- Sending milliseconds. Some languages hand you milliseconds by default; JavaScript's Date.now() is one. Divide by 1,000 and round to a whole number.
Cancelling a scheduled email
The cancel call is DELETE /conversations/messages/email/{emailMessageId}/schedule. It needs the conversations/message.write scope and a sub-account token, and the id in the path is the email message id.
Don't put the conversation id there. It's an easy id to reach for, and it's the wrong one. Store the email message id when you schedule the send, so cancelling doesn't depend on looking it up later.
HighLevel's cancel page is inconsistent with itself: one line says to post the messageId, while the route it documents uses DELETE and an emailMessageId. Follow the route and the method.
The call returns a status and a message when it fails. Log both and check them, rather than assuming a scheduled email is gone because the request didn't throw.
Cancel by the email's own id, with DELETE. The conversation id won't do.
Getting the sender name to show
emailFrom sets the sending address. In our builds, the recipient only saw a sender name when that field was written as a name and address together, in the form Name <address>.
That's our observation, not something HighLevel's API page spells out. Before you rely on it, send a test to an inbox you control and look at the sender line the way a contact would.
Inbound email with no direction
If you list messages and sort them into inbound and outbound by the direction field, watch for empty values. We've seen inbound email come back from the message list with a null direction.
Code that treats anything not marked inbound as outbound will misfile those messages, and anything that reacts to inbound mail will miss them. Treat a null direction as unknown, log it, and decide from the rest of the message what it is.
A check before you ship
Run through this list against a test contact before the integration touches real conversations.
- Send a reply with replyMessageId and threadId and confirm it lands inside the original thread.
- Schedule a send a few minutes out, in UTC seconds, and confirm it goes at the right minute.
- Schedule another, cancel it with DELETE and its email message id, and confirm it never sends.
- Check the sender line on a received test email.
- List messages and look for any with a null direction.
Rather have it set up for you?
Once we've had a 30-minute 1:1 with Rob, we wire your email sends, replies and scheduled messages into HighLevel and test each against a live inbox.
