fix(messages): normalize webhook reply references

This commit is contained in:
2026-07-12 22:17:04 +08:00
parent 32d3329d1b
commit 813faa4cd7
4 changed files with 182 additions and 27 deletions
@@ -3,7 +3,9 @@ package webhook
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/google/uuid"
@@ -538,8 +540,27 @@ func (p *IncomingPersister) createMessage(ctx context.Context, tx *gorm.DB, inbo
if len(msg.Attachments) > 0 {
contentAttrs["attachments"] = msg.Attachments
}
if msg.ReplyToID != "" {
contentAttrs["in_reply_to"] = msg.ReplyToID
if replyToExternalID := strings.TrimSpace(msg.ReplyToID); replyToExternalID != "" {
// Chatwoot stores provider reply references as in_reply_to_external_id,
// then resolves the same-conversation message by source_id before save.
// Keep in_reply_to strictly as the internal numeric message ID expected by
// the dashboard and its cursor-based message API.
var replyMessage model.Message
err := tx.WithContext(ctx).
Where("conversation_id = ? AND source_id = ?", conversation.ID, replyToExternalID).
First(&replyMessage).Error
if err == nil {
contentAttrs["in_reply_to"] = replyMessage.ID
contentAttrs["in_reply_to_external_id"] = replyMessage.SourceID
} else if errors.Is(err, gorm.ErrRecordNotFound) {
// Match Chatwoot's InReplyToMessageBuilder: unresolved references
// are normalized to null instead of being persisted as an invalid
// internal message ID.
contentAttrs["in_reply_to"] = nil
contentAttrs["in_reply_to_external_id"] = nil
} else {
return nil, err
}
}
message := model.Message{
ConversationID: conversation.ID,