fix(messages): resend failed messages through provider (#15432)
Failed messages on external provider-delivery channels can appear sent after an agent selects Retry even though the retry never reaches the provider. This change clears the stale provider delivery ID before retrying the existing message, allowing the channel service to create a fresh provider attempt and store its new ID. API and WebWidget messages are excluded because their `source_id` values may be client-supplied correlation identifiers and their retry jobs do not assign replacement provider IDs. Concurrent or stale retry requests are also guarded so only the request that successfully claims a failed message can enqueue delivery. Fixes https://github.com/chatwoot/chatwoot/issues/14120 Related: https://linear.app/chatwoot/issue/CW-6896/retrying-a-failed-whatsapp-message-silently-succeeds-locally-but-never ### Things to know - Applies to external provider-delivery channels, including direct WhatsApp and Twilio WhatsApp. - Preserves `source_id` for `Channel::Api` and `Channel::WebWidget`. - Reuses the existing Chatwoot message record while allowing the provider to return a new message ID. - Logs the cleared provider ID and Chatwoot message ID when a stale provider ID is removed. ### How to reproduce 1. Send a message that the provider accepts and later marks as failed through a status callback. 2. Select Retry on the failed message. 3. Observe that Chatwoot changes the message to sent but does not contact the provider because the previous provider message ID remains present. ### How to test 1. In an external provider inbox, retry a failed message that has an existing provider message ID. 2. Confirm the old provider ID is cleared before delivery and the retry reaches the provider. 3. Confirm the provider assigns a fresh message ID and subsequent status callbacks update the retried message. 4. Retry a failed API or WebWidget message and confirm its existing `source_id` is preserved. 5. Trigger concurrent retry requests for the same failed message and confirm only one delivery job is queued. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
This commit is contained in:
@@ -28,10 +28,7 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
||||
def retry
|
||||
return if message.blank?
|
||||
|
||||
service = Messages::StatusUpdateService.new(message, 'sent')
|
||||
service.perform
|
||||
message.update!(content_attributes: {})
|
||||
::SendReplyJob.perform_later(message.id)
|
||||
::SendReplyJob.perform_later(message.id) if claim_message_retry
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
@@ -67,6 +64,22 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
|
||||
def claim_message_retry
|
||||
message.with_lock do
|
||||
next false unless message.failed?
|
||||
|
||||
Messages::StatusUpdateService.new(message, 'sent').perform
|
||||
previous_source_id = message.source_id
|
||||
retry_attributes = { content_attributes: {} }
|
||||
retry_attributes[:source_id] = nil unless @conversation.inbox.api? || @conversation.inbox.web_widget?
|
||||
message.update!(retry_attributes)
|
||||
if retry_attributes.key?(:source_id) && previous_source_id.present?
|
||||
Rails.logger.info "Cleared older source ID #{previous_source_id} for message #{message.id}"
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :target_language, :status, :external_error)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user