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 <sojan@pepalo.com>
This commit is contained in:
Muhsin Keloth
2026-02-11 03:55:25 +05:30
committed by GitHub
parent e65ea24360
commit 0ad47d87f4

View File

@@ -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?