Hi, Iβm Mohammad Reza Kianifar, and this is a summary of my work contributing to Zulip during Google Summer of Code 2025.
My project, Migrate Zulipβs Direct Message Recipient Data Structures to a New Model, focused on migrating 1:1 conversations from the legacy Personal recipient model to the DirectMessageGroup model.
This migration enables:
- Per-conversation settings for all direct messages
- Enhanced features such as pinning/hiding DM conversations
- A unified recipient model across all direct messages
| Title | Migrate Zulipβs Direct Message Recipient Data Structures to a New Model |
| Size | Medium (175+ hrs) |
| Duration | May 2025 - September 2025 |
| Organization | Zulip |
| Issue | #25713 |
| Mentor | Sumanth Rao |
| Technologies | Python, PostgreSQL, Django, Database Migration |
| Contributor | Mohammad Reza Kianifar |
Each Zulip message has a sender and a recipient. Before this project, Zulip used three recipient types:
Personalβ Single user (1:1 and self DMs)Streamβ Channel/topic-based messagesDirectMessageGroupβ Group conversations (with more than one user))
The migration transitions 1:1 and self messages from type #1 to type #3 while maintaining full backward compatibility during the process.
Once complete, the legacl Personalβ type (including its code paths and related database columns) can be removed.
In this phase, I added the support for using DirectMessageGroup as a safe alternative to Personal for 1:1 and self DMs. These changes did not affect production yet, since such DirectMessageGroup models were not created at that time. The PRs including:
- direct_messages: Use DM groups for 1:1 or self DMs if present. #34307 - β
Merged
- A preparatory refactor for migrating the internal structure of
Recipientobjects for group DMs to use theDirectMessageGrouptype, not the legacyPersonaltype. This step has the message-sending code path check if aDirectMessageGroupeexists and prefer it if available.events about muted users.
- A preparatory refactor for migrating the internal structure of
- messages: Add unread 1:1/self DMs to
pm_dictif DM group exists. #34787 - β Merged- Overrides
get_raw_unread_databehavior to populatepm_dictfor 1:1 and self DMs when aDirectMessageGroupexists. This maintains the API compability.
- Overrides
- export_search: Support
--dmoption for 1:1 DMs using DM group. #34789 - β Merged- Updates the
export_searchcommand to first checks for aDirectMessageGroupfor the two users and uses it if present. If not, we fall back to the legacy logic.
- Updates the
- notification: Use existing email format for missed 1:1 DM via DM group. #34822 - β
Merged
- Renders the email notification for missed 1:1 direct messages using
DirectMessageGroupwith the same format as messages sent to aPersonalrecipient.
- Renders the email notification for missed 1:1 direct messages using
- push_notification: Use existing payload for 1:1 DM using DM group. #34864 - β
Merged
- Generates notification payloads for 1:1 messages via
DirectMessageGroupin the same format as those sent to aPersonalrecipient.
- Generates notification payloads for 1:1 messages via
- scheduled_messages: Set
read_by_senderfor self-DMs using DM group. #34842 - β Merged- Ensures
read_by_senderis set correctly for when scheduling a 1:1 message usingDirectMessageGroup.
- Ensures
- welcome_bot: Support auto-reply for 1:1 DMs using DM groups. #34852 - β
Merged
- Ensures Welcome Bot auto-replies to users in 1:1 DMs, including when
the
DirectMessageGrouprecipient model is used.
- Ensures Welcome Bot auto-replies to users in 1:1 DMs, including when
the
- report_message: Use existing template for reporting 1:1 DM via DM group. #34884 - β
Merged
- Builds the existing report message format for 1:1 DMs using
DirectMessageGroup.
- Builds the existing report message format for 1:1 DMs using
- analytics: Support DM groups in count message type query. #35348 - β
Merged
- Updates the analytics count query to treat DM groups of size 2 or less as private messages.
- Support
DirectMessageGroupwhen displaying scheduled messages to self. #35785 - β Merged- get_recipient_ids: Include user ID for self-messages using DM group.
- Adds user ID for self-messages in
get_recipient_idswhenDirectMessageGroupis used.
- Adds user ID for self-messages in
- scheduled_messages: Add support for self-DM identification.
- Shows "You" as the header for self-scheduled to improve the user experience. This is not only limited to when
DirectMessageGroupis used.
- Shows "You" as the header for self-scheduled to improve the user experience. This is not only limited to when
- get_recipient_ids: Include user ID for self-messages using DM group.
- settings: Add variable to control recipient type for 1:1 DMs. #35469 - π Open
- Adds
PREFER_DIRECT_MESSAGE_GROUPsetting to control the recipient type for 1:1 and self DMs. - Sets to
Falseby default. - Uses
@override_settings(PREFER_DIRECT_MESSAGE_GROUP=True)to test the new behaviour.
- Adds
- personal_recipient: Handle cases with none values. #34900 - π Open
- Handle cases where the
Personalrecipient isNoneto ensureDirectMessageGroupis a safe alternative option whenPersonalrecipient does not exist.
- Handle cases where the
- Import 1:1 DMs as
DirectMessageGroupif enabled. #35903 - π Open- import: Create DM group for 1:1 messages if enabled.
- Uses
DirectMessageGroupas the recipient type for 1:1 messages when importing from Mattermost and RocketChat.
- Uses
- slack: Refactor functions to use dataclasses.
- Uses
DirectMessageGroupas the recipient type for 1:1 messages when importing from Slack. - Creates dataclsses to be used as return types of import Slack functions.
- Uses
- import: Create DM group for 1:1 messages if enabled.
Here, I enabled DirectMessageGroup as the default recipient for 1:1 and self DMs, and created a backfill migration to update existing records. The PRs are:
- Transition 1:1 DMs to use direct message groups when enabled #35769 - π Open
- settings: Enable preference for DM group over personal recipient.
- Sets the
PREFER_DIRECT_MESSAGE_GROUPsetting variable asTrue.
- Sets the
- direct_messages: Create DM groups for 1:1 DMs if enabled.
- Updates the code path of selecting the recipient type for messages to create a
DirectMessageGroupfor 1:1 and self DMs if the setting variable is enabled. Otherwise, falls back to the legacyPersonalrecipient.
- Updates the code path of selecting the recipient type for messages to create a
- settings: Enable preference for DM group over personal recipient.
- migrations: Add backfill to update personal DMs to rely on DM group.
#35795 - π Open
- Updates recipients of 1:1 or self messages to rely on
DirectMessageGroup. The affected models includeDraft,ScheduledMessage,Message, andArchivedMessage. I fetch all records and update the recipient type in batches for all direct messages between two (or one) users.
- Updates recipients of 1:1 or self messages to rely on
Once migration is complete, legacy Personal code paths can be removed. The related PRs are:
- personal_recipients: Stop creating when DM groups preferred for 1:1. #35347 - π Open
- Stops creating
Personalrecipients and their subscriptions whenPREFER_DIRECT_MESSAGE_GROUPisTrue. - Affects user creation in single/bulk operations and data imports from other platforms.
- Stops creating
- personal_recipients: Delete legacy code paths. #36107 - π Open
- Deletes all code paths where the legacy
Personalrecipient was used. - Removes the
PREFER_DIRECT_MESSAGE_GROUPsetting variable.
- Deletes all code paths where the legacy
- migrations: Delete personal recipients. (TODO)
- Deletes all recipients of type
Personal - Drops
UserProfile.recipientfield.
- Deletes all recipients of type
Based on the community discussions in the Zulip channel, we decided to add three fields to the DirectMessageGroup model for efficiency and future use. This work is done in the following PR, including four commits:
- Add
first_message,last_message, andtotal_messagestoDirectMessageGroup#36045 - π Open- dm_groups: Add fields for message statistics tracking.
- Adds the new fields to the
DirectMessageGroupmodel + the migration.
- Adds the new fields to the
- message_send: Update direct message group statistics.
- Sets/Updates the new fields when new direct messages are sent.
- retention: Update DM group statistics on archive and restore.
- Updates the fields when archiving/restoring direct messages.
- migrations: Add backfill to populate DM group statistics.
- Adds backfill migration to populate the new fields for existing
DirectMessageGroups.
- Adds backfill migration to populate the new fields for existing
- dm_groups: Add fields for message statistics tracking.
I will continue working on the pending PRs to get them merged, finalize the migration, and complete clean-up tasks.
Working on this project has been one of the most impactful experiences of my career. I learned a great deal about large-scale migrations, the rebase Git strategy for managing PRs and commits, and Zulipβs development practices and architecture.
Iβve grown as a software engineer and look forward to continuing my contributions to Zulip, exploring other areas of the codebase, and learning from the amazing community here.
I would like to thank Google and Zulip for giving me this opportunity to work on such an exciting project.
A special thanks to Tim Abbott for his invaluable feedback and collaboration, which greatly influenced the quality and progress of this work.
I truly appreciate the constant support, guidance, and accurate reviews of to my mentor Sumanth Rao.
I also sincerely thank Rohan Gudimetla and the entire Zulip community for their helpful reviews, feedback, and encouragement throughout the project.
