From 0ad47d87f48f64454c1541cf2283402403fb9c24 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 11 Feb 2026 03:55:25 +0530 Subject: [PATCH] fix: Use Faraday for Telegram document uploads to fix large file failures (#13397) Fixes https://linear.app/chatwoot/issue/CW-6415/sending-large-attachments-11mb-via-telegram-channels-fails-with-http #### Issue Sending large attachments (~11MB) via Telegram channels fails with HTTP 502 (Bad Gateway) and 413 (Request Entity Too Large) errors. The issue is caused by HTTParty's built-in multipart encoding, which reads the entire file into an in-memory string before constructing the request body. For large files, this produces a malformed multipart request that Telegram's API proxy rejects. #### Solution Replace HTTParty with Faraday + multipart-post (both already available in the project) for the sendDocument multipart upload. The multipart-post gem streams file content directly from disk into the HTTP request, producing a correctly formed multipart body that Telegram accepts for large files. --------- Co-authored-by: Sojan Jose --- .../telegram/send_attachments_service.rb | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/app/services/telegram/send_attachments_service.rb b/app/services/telegram/send_attachments_service.rb index 5ba8fd4ae..7b66efd83 100644 --- a/app/services/telegram/send_attachments_service.rb +++ b/app/services/telegram/send_attachments_service.rb @@ -1,3 +1,5 @@ +require 'faraday/multipart' + # Telegram Attachment APIs: ref: https://core.telegram.org/bots/api#inputfile # Media attachments like photos, videos can be clubbed together and sent as a media group @@ -111,17 +113,33 @@ class Telegram::SendAttachmentsService def send_file(chat_id, file_path, reply_to_message_id) File.open(file_path, 'rb') do |file| - HTTParty.post("#{channel.telegram_api_url}/sendDocument", - body: { - chat_id: chat_id, - **business_connection_body, - document: file, - reply_to_message_id: reply_to_message_id - }, - multipart: true) + file_name = File.basename(file_path) + mime_type = Marcel::MimeType.for(name: file_name) || 'application/octet-stream' + + payload = { chat_id: chat_id, document: Faraday::Multipart::FilePart.new(file, mime_type, file_name) } + payload[:reply_to_message_id] = reply_to_message_id if reply_to_message_id + payload.merge!(business_connection_body) + + response = multipart_post_connection.post("#{channel.telegram_api_url}/sendDocument", payload) + parse_faraday_response(response) end end + def multipart_post_connection + @multipart_post_connection ||= Faraday.new do |f| + f.request :multipart + f.options.timeout = 300 + f.options.open_timeout = 60 + end + end + + def parse_faraday_response(response) + parsed = JSON.parse(response.body) + OpenStruct.new(success?: response.success?, parsed_response: parsed) + rescue JSON::ParserError + OpenStruct.new(success?: false, parsed_response: { 'ok' => false, 'error_code' => response.status, 'description' => response.reason_phrase }) + end + def handle_response(response) return true if response.success?