Data Flow for Sending Media Files in Telegram Desktop: A Complete Technical Guide

Telegram Desktop sends media files through a seven-stage pipeline that prepares the file, uploads it to Telegram's servers, constructs an MTProto inputMedia object, and transmits it via MTPmessages_SendMedia before processing the server's Updates response.

The data flow for sending media files like photos, documents, and voice messages in Telegram Desktop follows a strict pipeline from UI selection to network transmission. This article examines the exact implementation in the telegramdesktop/tdesktop repository, tracing how user-selected files become encrypted MTProto messages. Understanding this data flow is essential for developers contributing to the client or building compatible implementations.

Overview of the Media Sending Pipeline

When a user attaches a media file, the application executes a well-defined sequence across multiple subsystems:

  1. UI Capture – The attach widget captures file selection and triggers preparation
  2. Media Preparation – Files are validated, classified, and wrapped in a PreparedList
  3. Upload Streaming – Storage::Uploader streams file parts to Telegram's upload infrastructure
  4. Input Media Construction – Uploaded files are converted to MTP_inputMedia* objects
  5. Request Assembly – ApiWrap::sendMedia serializes captions, entities, and send options
  6. Network Dispatch – Histories::sendPreparedMessage transmits MTPmessages_SendMedia
  7. Response Handling – Server-returned MTPupdates are applied to the local history

Step-by-Step Data Flow Analysis

1. UI File Selection and Media Preparation

The process begins in Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp. When the user presses Send, the preview widget invokes the preparation layer to transform raw file paths into upload-ready structures.

The UI layer does not handle validation directly; instead, it delegates to Storage::PrepareMediaList to ensure consistent handling across all entry points.

2. File Validation and Metadata Extraction

The core preparation logic resides in Telegram/SourceFiles/storage/storage_media_prepare.cpp. The PrepareMediaList function validates each path, classifies the media type, and extracts essential metadata:

PreparedList PrepareMediaList(
        const QStringList &files,
        int previewWidth,
        bool premium);

This function performs several critical operations:

  • Validates local file existence, non-empty paths, and size limits
  • Classifies files as photos, videos, music, or generic documents
  • Extracts dimensions and MIME types via FileLoadTask::ReadMediaInformation
  • Returns a PreparedList containing PreparedFile objects that drive the subsequent upload

Files that fail validation are flagged with specific error codes, allowing the UI to present appropriate feedback before network operations begin.

3. Uploading to Telegram Servers

Once prepared, files enter the upload subsystem in Telegram/SourceFiles/storage/file_upload.cpp. The Uploader class manages the actual data transmission:

  • Uploader::Entry determines whether to treat the file as a photo, document, or audio, selecting appropriate part containers (fileparts vs thumbparts)
  • The uploader handles multipart streaming, retry logic, and encryption of file segments
  • Upon completion, Uploader::photoReady or Uploader::documentReady emits an UploadedMedia signal

The upload layer operates asynchronously, emitting completion signals that trigger the next phase without blocking the UI thread.

4. Constructing MTProto Input Media Objects

When the upload completes, Telegram/SourceFiles/apiwrap.cpp receives the UploadedMedia signal and converts the uploaded data into MTProto-compatible structures. Two specialized functions handle this conversion:

void ApiWrap::sendUploadedPhoto(
        FullMsgId fullId,
        const UploadedPhoto &info,
        const Api::SendOptions &options);

void ApiWrap::sendUploadedDocument(
        FullMsgId fullId,
        const UploadedDocument &info,
        const Api::SendOptions &options);

These functions construct MTP_inputMediaPhoto or MTP_inputMediaDocument objects, embedding the file references returned by Telegram's upload servers. Both functions ultimately forward to the generic sendMedia method, ensuring uniform handling of captions, entities, and send options regardless of media type.

5. Building the Send Media Request

The ApiWrap::sendMedia function in Telegram/SourceFiles/apiwrap.cpp serves as the central coordinator for assembling the final network payload:

void ApiWrap::sendMedia(
        not_null<HistoryItem*> item,
        const MTPInputMedia &media,
        Api::SendOptions options,
        Fn<void(bool)> done);

This method:

  • Generates a cryptographically secure randomId and registers it via registerMessageRandomId
  • Serializes the message caption, entities, and formatting
  • Encodes send options including silent mode, scheduling, message effects, and paid star transactions
  • Delegates to the history subsystem for actual transmission

6. History Subsystem and Network Transmission

The final network dispatch occurs in Telegram/SourceFiles/data/data_histories.cpp via Histories::sendPreparedMessage:

int Histories::sendPreparedMessage(
        History *history,
        const ReplyTo *replyTo,
        uint64 randomId,
        MTP::Request<MTPmessages_SendMedia> request,
        Fn<void(const MTPUpdates&, const MTP::Response&)> done,
        Fn<void(const MTP::Error&, const MTP::Response&)> fail);

This function:

  • Inserts a pending message into the local history cache for immediate UI feedback
  • Dispatches the MTPmessages_SendMedia request through the MTProto connection layer
  • Registers success and failure callbacks to handle the asynchronous server response

The history subsystem maintains message state throughout the transmission lifecycle, ensuring that pending messages survive application restarts and network interruptions.

7. Server Response and UI Updates

Upon successful transmission, the server returns MTPupdates containing the finalized message objects. The client processes these updates in Telegram/SourceFiles/history/history.cpp:

  • History::addNewMessage creates concrete HistoryItem instances (photos, documents, voice notes) from the server data
  • The pending local message is replaced with the authoritative server version
  • UI widgets receive update notifications and repaint to reflect the confirmed message state

Practical Implementation: Sending a Photo Programmatically

The following example demonstrates the complete data flow when sending a photo from a custom action:

void sendMyPhoto(
        not_null<Main::Session*> session,
        const QString &filePath,
        const QString &caption) {

    // 1️⃣ Prepare the media list (single file → photo)
    const auto list = Storage::PrepareMediaList(
            QStringList{filePath},
            st::sendMediaPreviewSize, // preview width from style
            session->premium());      // premium flag

    if (list.error != Storage::PreparedList::Error::None) {
        LOG(("Failed to prepare media: %1").arg(list.error));
        return;
    }

    // 2️⃣ Upload via the uploader (creates an UploadedMedia signal)
    session->uploader().upload(
        std::make_shared<Storage::FilePrepareResult>(list.files.front()),
        [&](const Storage::UploadedMedia &media) {
            // 3️⃣ Build a HistoryItem that will be sent
            const auto item = session->data().createItem(
                session->data().peer( // target chat
                    session->user().id),
                QString(), // empty text, media will be attached
                { .options = {} });

            // 4️⃣ Call ApiWrap – the uploader already called sendUploadedPhoto,
            //    but we can also call it ourselves:
            session->api().sendUploadedPhoto(
                item->fullId(),
                media.photoInfo,
                Api::SendOptions()); // default options
        });
}

This snippet directly utilizes Storage::PrepareMediaList for validation, initiates upload via Uploader, and invokes ApiWrap::sendUploadedPhoto upon completion. All thumbnail generation, multipart streaming, and MTProto serialization occur within the framework's internal pipeline.

Summary

  • Preparation Phase: Storage::PrepareMediaList in storage_media_prepare.cpp validates files and extracts metadata before any network activity begins.
  • Upload Phase: The Uploader class in file_upload.cpp handles encrypted multipart uploads and emits UploadedMedia signals upon completion.
  • API Construction: ApiWrap methods (sendUploadedPhoto, sendUploadedDocument) convert uploaded data into MTP_inputMedia* objects for the MTProto protocol.
  • Network Dispatch: Histories::sendPreparedMessage in data_histories.cpp transmits MTPmessages_SendMedia requests and manages pending message states.
  • Response Handling: Server MTPupdates are processed by the history subsystem to finalize message objects and update the UI.

Frequently Asked Questions

How does Telegram Desktop handle file uploads for large media files?

Large files are automatically divided into parts by the Uploader class in Telegram/SourceFiles/storage/file_upload.cpp. The system streams these parts sequentially to Telegram's upload servers, with Uploader::Entry managing separate containers for file data and thumbnail previews. This multipart approach allows transmission to resume after network interruptions without re-uploading completed segments.

What is the difference between sendUploadedPhoto and sendUploadedDocument?

sendUploadedPhoto constructs an MTP_inputMediaPhoto object optimized for image files with automatic compression and dimension metadata, while sendUploadedDocument creates an MTP_inputMediaDocument suitable for files requiring preservation of exact bytes, such as PDFs, voice messages, or video files. Both functions reside in Telegram/SourceFiles/apiwrap.cpp and converge on the common sendMedia path after constructing their respective input media types.

How does the MTProto API receive media messages from Telegram Desktop?

The client transmits media via the MTPmessages_SendMedia method, constructed in Telegram/SourceFiles/data/data_histories.cpp by sendPreparedMessage. This request includes the MTPInputMedia reference (pointing to the uploaded file on Telegram's servers), message caption, entities, and flags for options like silent sending or scheduling. The MTProto layer encrypts this payload and delivers it to Telegram's datacenters.

Where is the media upload state managed in the Telegram Desktop codebase?

Upload state is primarily managed by the Storage::Uploader class in Telegram/SourceFiles/storage/file_upload.cpp, which tracks progress for each Uploader::Entry. The history subsystem in Telegram/SourceFiles/data/data_histories.cpp maintains the association between pending local messages and their corresponding upload operations, ensuring that UI state remains synchronized with background transmission progress.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →