Contributing to Thunderbird for Android

Welcome to the Thunderbird for Android project! We’re excited to have you here and welcome your contributions.

Getting Started

Before you start contributing, please take a moment to familiarize yourself with the following:

Bug Reports and Feature Requests

If you encounter a bug or have a feature request, please follow these steps:

  • Search the existing issues to see if your issue or feature has already been reported.
  • If you can’t find an existing issue, please open a new issue on GitHub.

Translations

If you’d like to help to translate K-9 Mail / Thunderbird for Android, please visit the Weblate - K-9 Mail/Thunderbird project.

Contributing Code

Thank you for your willingness to contribute code! Here’s how you can get started:

1. Find an issue:

2. Discuss your plan:

  • Leave a comment on the issue you want to work on, explaining what you plan to do. This helps avoid duplicate work and gets you feedback from the team.

3. Fork the repository:

  • Create your own fork of the Thunderbird for Android repository on GitHub.

4. Create a branch:

  • Start a new branch from the main branch to keep your changes separate.
  • Name your branch descriptively (e.g., fix-issue-123 or add-feature-xyz).

5. Make your changes:

6. Test your changes:

  • Run the project’s tests to make sure everything works and that your changes don’t introduce any regressions.
  • If applicable, write new tests to cover your changes.

7. Push your changes:

  • Upload your branch to your forked repository.

8. Open a pull request:

  • Create a pull request to merge your changes into the main project.
  • Provide a clear and concise description of your changes, including:
    • A reference to the issue you’re addressing.
    • A summary of the changes you made.
    • Any relevant screenshots or testing results.

Thank You!

Thank you for taking the time to contribute to Thunderbird for Android! We appreciate your help in making the project better and more useful for everyone.

Last change: 2025-05-13, commit: eee090c

Repository structure

The project is divided into several directories below which are nested gradle projects.

app

This contains the highest level code such as UI and core logic.

backend

APIs for sending and receiving messages

mail

Low level code for dealing with internet mail protocols

plugins

Additional, standalone, libraries used by Thunderbird for Android

modules

Walkthrough

To help you understand the design, the following sequence diagrams show typical flows through the classes. Each class is colour-coded by its top-level project.

Reading email

read email sequence

read email classes

Sending email

send email sequence

Last change: 2025-05-13, commit: eee090c

Thunderbird for Android Release Documentation

Please see the sub-pages for release documentation

Releases

Thunderbird for Android follows a release train model to ensure timely and predictable releases. This model allows for regular feature rollouts, stability improvements, and bug fixes.

Branches in the Release Train Model

Daily

Daily builds are used for initial testing of new features and changes. Feature flags are additionally used to work on features that are not yet ready for consumption.

  • Branch: main
  • Purpose: Active development of new features and improvements
  • Release Cadence: Daily
  • Audience: Developers and highly technical users who want to test the bleeding edge of Thunderbird. Daily builds are unstable and not recommended for production use.
  • Availability: Daily builds are available on the Play Store internal channel. APKs are available on ftp.mozilla.org.

Beta

After features are stabilized in Daily, they are merged into the Beta branch for broader testing. Uplifts are limited to bug/security fixes only. The Beta branch serves as a preview of what will be included in the next stable release, allowing for user feedback and final adjustments before general availability.

  • Branch: beta
  • Purpose: Pre-release testing
  • Release Cadence: Weekly with the option to skip if not needed
  • Merge Cadence: Every 2 Months
  • Audience: Early adopters and testers. Testers are encouraged to provide error logs and help reproduce issues filed.
  • Availability: Beta builds are available from the Play Store and F-Droid.

Release

This branch represents the stable version of Thunderbird, which is released to the public. It is tested and suitable for general use. Bug fixes and minor updates are periodically applied between major releases. Uplifts to Release are limited to stability/security fixes only.

  • Branch: release
  • Purpose: Stable releases
  • Release Cadence: Major releases every 2 months. Minor releases every 2 weeks with the option to skip if not needed.
  • Merge Cadence: Every 2 months
  • Audience: General users. Users may be filing bug reports or leaving reviews to express their level of satisfaction.
  • Availability: Release builds are available from the Play Store and F-Droid.

Example Feature Release Flow

  1. A new feature is developed and merged via pull requests into the main branch.
  2. Every 2 months, main is merged into the beta branch for external testing and feedback.
  3. Every 2 months, beta is merged into the release branch, and a release is made available to all users.

Example Bug Release Flow

  1. A high-impact bug is fixed and merged via pull request into the main branch.
  2. After it has received adequate testing on daily, the fix is cherry-picked (uplifted) to the beta branch and released in the next scheduled beta.
  3. After it has received adequate testing on beta, the fix is cherry-picked (uplifted) to the release branch and released in the next stable minor release.

Sample Release Timeline

MilestoneDetailsDate
TfA 11.0a1 startsFeb 28
TfA merge 11.0a1 main->betaMay 2
TfA 11.0b1May 5
TfA 11.0bXIf neededMay 12
TfA 11.0bXIf neededMay 19
TfA 11.0bXIf neededMay 26
TfA 11.0bXIf neededJun 2
TfA 11.0bXIf neededJun 9
TfA 11.0bXIf neededJun 16
TfA 11.0bXIf neededJun 23
TfA 11.0bXIf neededJun 30
TfA merge 11.0bX beta->releaseJun 30
TfA 11.0Jul 7
TfA 11.XIf neededJul 21
TfA 11.XIf neededAug 4
TfA 11.XIf neededAug 18
TfA 11.XIf neededSep 1

Feature Flags

Thunderbird for Android uses Feature Flags to disable features not yet ready for consumption.

  • On main, feature flags are enabled as soon as developers have completed all pull requests related to the feature.
  • On beta, feature flags remain enabled unless the feature has not been fully completed and the developers would like to pause the feature.
  • On release, feature flags are disabled until an explicit decision has been made to enable the feature for all users.

Versioning System

Version Names

Thunderbird for Android stable release versions follow the X.Y format, where:

  • X (Major version): Incremented for each new release cycle.
  • Y (Patch version): Incremented when changes are added to an existing major version.

For beta builds, the suffix b1 is appended, where the number increments for each beta. For daily builds, the suffix a1 is appended, which remains constant.

Version Codes

The version code is an internal version number for Android that helps determine whether one version is more recent than another.

The version code for beta and release is an integer value that increments for each new release.

The version code for daily is calculated based on the date and has the format yDDDHHmm:

  • y: The number of years since a base year, with 2023 as the starting point (e.g., 2024 is 1)
  • DDD: The day of the year in 3 digits, zero-padded
  • HH: The hour of the day in 2 digits (00–23)
  • mm: The minute of the hour in 2 digits

For example:

  • 2024-02-09 16:451 | 040 | 16 | 4510401645
  • 2025-10-12 09:232 | 285 | 09 | 2322850923
  • 2122-02-09 16:4599 | 040 | 16 | 45990401645

Merge Days

Active development occurs on the main branch and becomes part of daily. Every 2 months:

  1. main is merged into beta, for testing.
  2. beta is merged into release, making it publicly available.

On the former, main carries over to beta, where the community can test the changes as part of “Thunderbird Beta for Testers” (net.thunderbird.android.beta) until the next merge day. On the latter, code that was in beta goes to release, where the general population receives product updates (net.thunderbird.android).

When a merge occurs, the version name is carried forward to the next branch. However, the alpha and beta suffixes are removed/reset accordingly. For example, let’s say we are shortly before the Thunderbird 9.0 release. The latest releases were Thunderbird 8.2, Thunderbird Beta 9.0b4, and Thunderbird Daily 10.0a1. Here is what happens:

  • The beta branch is merged to release. The resulting version on release changes from 8.2 to 9.0.
  • The main branch is merged to beta. The resulting version on beta changes from 9.0b4 to 10.0b1
  • The main branch version number is changed from 10.0a1 to 11.0a1

While the version name changes, it must be ensured that the version code stays the same for each branch. Our application IDs are specific to the branch they are on. For example:

  • Beta always uses net.thunderbird.android.beta as the app ID. Let’s say the version code is 20 at 9.0b4, it will be 21 at 10.0b1.
  • Likewise, when 9.0b4 becomes 9.0, if the version code on beta is 20 and on release it is 12, then 9.0 becomes 13 and not 21.

Milestones

We’re using GitHub Milestones to track work for each major release. There is only one milestone for the whole major release, so work going into 9.0 and 9.1 would both be in the “Thunderbird 9” milestone. Each milestone has the due date set to the anticipated release date.

There are exactly three open milestones at any given time, some of our automation depends on this being the case. The milestone with the date furthest into the future is the target for the main branch, the one closest is the target for the release branch. When an uplift occurs, the milestone is changed to the respective next target.

Learn more on the milestones page

Merge Process

The merge process enables various benefits, including:

  • Carrying forward main branch history to beta, and beta branch history to release.
  • No branch history is lost.
  • Git tags are retained in the git log.
  • Files/code that is unique per branch can remain that way (e.g. notes files such as changelog_master.xml, version codes).

The following steps are taken when merging main into beta:

  1. Lock the main branch with the ‘CLOSED TREE (main)’ ruleset
  2. Send a message to the #tb-mobile-dev:mozilla.org matrix channel to let them know:
  • You will be performing the merge from main into beta
  • The main branch is locked and cannot be changed during the merge
  • You will let them know when the merge is complete and main is re-opened
  1. Review merge results and ensure correctness
  2. Ensure feature flags are following the rules
  3. Push the merge
  4. Submit a pull request that increments the version in main
  5. Open a new milestone for the new version on github
  6. Once the version increment is merged into main, unlock the branch
  7. Send a message to the #tb-mobile-dev:mozilla.org channel to notify of merge completion and that main is re-opened

The following steps are taken when merging beta into release:

  1. Send a message to the #tb-mobile-dev:mozilla.org matrix channel to let them know:
  • You will be performing the merge from beta into release
  • You will let them know when the merge is complete
  1. Review merge results and ensure correctness
  2. Ensure feature flags are following the rules
  3. Push the merge
  4. Close the milestone for the version that was previously in release
  5. Send a message to the #tb-mobile-dev:mozilla.org channel to notify of merge completion

Merges are performed with the do_merge.sh script.

The following will merge main into beta: scripts/ci/merges/do_merge.sh beta

And the following will merge beta into release: scripts/ci/merges/do_merge.sh release

Be sure to review merge results and ensure correctness before pushing to the repository.

Files of particular importance are:

  • app-k9mail/build.gradle.kts
  • app-thunderbird/build.gradle.kts
  • app-k9mail/src/main/res/raw/changelog_master.xml

These build.gradle.kts files must be handled as described in “Merge Days” section above. This is part of the do_merge.sh automation. The app-k9mail/src/main/res/raw/changelog_master.xml should not include any beta notes in the release branch.

Branch Uplifts

If the urgency of a fix requires it to be included in the Beta or Release channel before the next merge, the uplift process is followed. If possible, uplifts should be avoided and patches should “ride the train” instead, following the merge day cycle.

Uplift Criteria

Beta uplifts should:

  • Be limited to bug/security fixes only (features ride the train).
  • Not change any localizable strings
  • Have tests, or a strong statement of what can be done in the absence of tests.
  • Have landed in main and stabilized on the daily channel.
  • Have a comment in the GitHub issue assessing performance impact, risk, and reasons the patch is needed on beta.

Release uplifts should additionally:

  • Be limited to stability/security fixes only (features ride the train).
  • Have landed in beta and stabilized on the beta channel.

Uplift Process

  1. The requestor adds the “task: uplift to beta” or “task: uplift to release” label to a merged pull request.
  2. The requestor makes a comment in the associated issue with the Approval Request Comment template filled out.
  3. The release driver reviews all uplift requests and, retaining the label for approved uplifts and removing the label for rejected uplifts.
  4. The release driver runs the Uplift Merges action for the specified target branch, which will remove the label, adjust the milestone, cherry-pick the commits, and push to the target branch.

Template for uplift requests:

[Approval Request Comment]
Original Issue/Pull request:
Regression caused by (issue #):
User impact if declined:
Testing completed (on daily, etc.):
Risk to taking this patch (and alternatives if risky):

Releases

Releases for both K-9 and Thunderbird for Android are automated with github actions. Daily builds are scheduled with the Daily Builds action and all builds are performed by the Shippable Build & Signing action.

For the historical manual release process, see Releasing.

Release Process

These are the general steps for a release:

  1. Perform merge or uplifts. Each release is the result of either a merge or uplift.
  2. Draft release notes at thunderbird-notes.
  3. Trigger build via the Shippable Build & Signing action.
  4. Review the build results by reviewing the action summary and the git commits resulting from the build.
    • Make sure the version code is incremented properly and not wildly off
    • Ensure the commits are correct
    • Ensure the symlink app-metadata points to the right product at this commit
  5. Test the build in the internal testing track
    • Release versions should be thoroughly tested with the test plan in Testrail
    • Beta versions only require a basic smoke test to ensure it installs
  6. Promote TfA and K-9 releases to production track in Play Store.
    • Set rollout to a low rate (generally 10-30%).
    • Betas are only released for TfA. K-9 beta users are advised to use Thunderbird.
  7. Wait for Play Store review to complete.
    • Release versions of TfA and K-9 have managed publishing enabled. Once the review has completed you need to publish the release
    • Beta versions of TfA do not have managed publishing enabled. It will be available once Google has reviewed, even on a weekend.
  8. Update F-Droid to new TfA and K-9 releases by sending a pull request to fdroiddata
  9. Send community updates to Matrix channels, and beta or planning mailing lists as needed.
  10. Approximately 24 hours after initial release to production, assess the following before updating rollout to a higher rate:
    • Crash rates, GitHub issues, install base, and reviews.
Last change: 2025-05-13, commit: eee090c

Release Automation Setup

Release automation is triggered by the workflow_dispatch event on the “Shippable Build & Signing” workflow. GitHub environments are used to set configuration variables and secrets for each application and release type.

Automatic setup

There is a script available for automatic setup, which is helpful if you want to replicate this on your own repository for devlopment. Please see /scripts/ci/setup_release_automation.

You can run it using:

python -m venv venv
source venv/bin/activate
pip install requests pynacl
cd .signing
python ../scripts/ci/setup_release_automation -r yourfork/thunderbird-android

You will need the following files:

  • The signing keys with their default filenames
  • A matrix-account.json with the following keys:
{
  "homeserver": "matrix-client.matrix.org",
  "room": "room id here",
  "token": "matrix token here",
  "userMap": {
    "github_username": "@matrix_id:mozilla.org"
  }
}
  • play-store-account.json with the service account json that will do the uploads
  • thunderbird-mobile-gh-releaser-bot.clientid.txt as a simple file with the client ID of the releaser bot (you can skip this to use GitHub Actions as the user)
  • thunderbird-mobile-gh-releaser-bot.pem with the private key of the releaser bot

Build Environments

Build environments determine the configuration for the respective release channel. The following are available:

  • thunderbird_beta
  • thunderbird_daily
  • thunderbird_release

The following (non-sensitive) variables have been set:

  • RELEASE_TYPE: daily | beta | release
  • MATRIX_INCLUDES: A JSON string to determine the packages to be built

The following MATRIX_INCLUDES would build an apk and aab for Thunderbird, and an apk for K-9 Mail.

[
  { "appName": "thunderbird", "packageFormat": "apk", "packageFlavor": "foss" },
  {
    "appName": "thunderbird",
    "packageFormat": "bundle",
    "packageFlavor": "full"
  },
  { "appName": "k9mail", "packageFormat": "apk", "packageFlavor": "foss" }
]

The environments are locked to the respective branch they belong to.

Signing Environments

These environments contain the secrets for signing. Their names follow this pattern:

<appName>_<releaseType>_<packageFlavor>
thunderbird_beta_full
thunderbird_beta_foss
k9mail_beta_foss

The following secrets are needed:

  • SIGNING_KEY: The base64 encoded signing key, see https://github.com/noriban/sign-android-release for details
  • KEY_ALIAS: The alias of your signing key
  • KEY_PASSWORD: The private key password for your signing keystore
  • KEY_STORE_PASSWORD: The password to your signing keystore

The environments are locked to the respective branch they belong to.

Publishing Hold Environment

The “publish_hold” is shared by all application variants and is used by the “pre_publish” job. It has no secrets or variables, but “Required Reviewers” is set to trusted team members who oversee releases. The effect is that after package signing completes, the publishing jobs that depend on it will not run until released manually.

publish hold

Github Releases Environment

This environment will create the github release. It uses actions/create-github-app-token to upload the release with limited permissions.

  • RELEASER_APP_CLIENT_ID: Environment variable with the OAuth Client ID of the GitHub app
  • RELEASER_APP_PRIVATE_KEY: Secret with the private key of the app

The releases environment is locked to the release, beta and main branches.

If you leave out the environment, the Github Actions user will be used.

Matrix Notify Environment

This environment will notify about build updates. It requires the following keys:

  • MATRIX_NOTIFY_TOKEN: The Matrix token of the user
  • MATRIX_NOTIFY_HOMESERVER: The homeserver for the account
  • MATRIX_NOTIFY_ROOM: The room id to notify in
  • MATRIX_NOTIFY_USER_MAP: A json object that maps github usernames to matrix ids

If you leave out this environment, no notifications will be sent.

Last change: 2025-05-13, commit: eee090c

Create K-9 Mail releases

This document contains the historical manual release process for K-9 Mail. Please use the automated process instead. We're keeping this around in case we need to do a manual release.

One-time setup

  1. Create a .signing folder in the root of the Git repository, if it doesn’t exist yet.
  2. Download the k9-release-signing.jks and k9.release.signing.properties files from 1Password and place them in the .signing folder.

Example <app>.<releaseType>.signing.properties file:

<app>.<releaseType>.storeFile=<path to keystore "../.signing/k9mail.jks">
<app>.<releaseType>.storePassword=<storePassword>
<app>.<releaseType>.keyAlias=<keyAlias>
<app>.<releaseType>.keyPassword=<keyPassword>
  • <app> is the short name of the app, e.g. k9
  • <releaseType> is the type of release, e.g. release

One-time setup for F-Droid builds

  1. Install fdroidserver by following the installation instructions.

    1. On MacOS, it’s best to install the latest version from source, because the version in Homebrew has some issues.
      1. Install the android command line tools if not available already.

        brew install --cask android-commandlinetools
        
      2. Install latest fdroidserver from source:

        python -m venv fdroidserver-env
        source fdroidserver-env/bin/activate
        pip install git+https://gitlab.com/fdroid/fdroidserver.git
        
      3. To use fdroidserver from the command line, you need to activate the virtual environment before each use:

        source fdroidserver-env/bin/activate
        
      4. To deactivate the virtual environment:

        deactivate
        
  2. Sign up for a Gitlab account and fork the fdroiddata repository.

  3. Clone your fork of the fdroiddata repository.

Release a beta version

  1. Update versionCode and versionName in app-k9mail/build.gradle.kts

  2. Create change log entries in

    • app-k9mail/src/main/res/raw/changelog_master.xml
    • app-metadata/com.fsck.k9/en-US/changelogs/${versionCode}.txt Use past tense. Try to keep them high level. Focus on the user (experience).
  3. Update the metadata link to point to K-9 Mail’s data: ln --symbolic --no-dereference --force app-metadata/com.fsck.k9 metadata

  4. Commit the changes. Message: “Version $versionName”

  5. Run ./gradlew clean :app-k9mail:assembleRelease --no-build-cache --no-configuration-cache

  6. Update an existing installation to make sure the app is signed with the proper key and runs on a real device.

    adb install -r app-k9mail/build/outputs/apk/release/app-k9mail-release.apk
    
  7. Tag as $versionName, e.g. 6.508

  8. Copy app-k9mail/build/outputs/apk/release/app-k9mail-release.apk as k9-${versionName}.apk to Google Drive (MZLA Team > K9 > APKs)

  9. Change versionName in app-k9mail/build.gradle.kts to next version name followed by -SNAPSHOT

  10. Commit the changes. Message: “Prepare for version $newVersionName”

  11. Update gh-pages branch with the new change log

  12. Push main branch

  13. Push tags

  14. Push gh-pages branch

Create release on GitHub

  1. Go to https://github.com/thunderbird/thunderbird-android/tags and select the appropriate tag
  2. Click “Create release from tag”
  3. Fill out the form
    • Click “Generate release notes”
    • Replace contents under “What’s changed” with change log entries
    • Add GitHub handles in parentheses to change log entries
    • If necessary, add another entry “Internal changes” (or similar) so people who contributed changes outside of the entries mentioned in the change log can be mentioned via GitHub handle.
    • Attach the APK
    • Select “Set as a pre-release”
    • Click “Publish release”

Create release on F-Droid

  1. Fetch the latest changes from the fdroiddata repository.

  2. Switch to a new branch in your copy of the fdroiddata repository.

  3. Edit metadata/com.fsck.k9.yml to create a new entry for the version you want to release. Usually it’s copy & paste of the previous entry and adjusting versionName, versionCode, and commit (use the tag name). Leave CurrentVersion and CurrentVersionCode unchanged. Those specify which version is the stable/recommended build.

    Example:

    - versionName: "${versionName}"
      versionCode: ${versionCode}
      commit: "${tagName}"
      subdir: app-k9mail
      gradle:
        - yes
      scandelete:
        - build-plugin/build
    
  4. Commit the changes. Message: “Update K-9 Mail to $newVersionName (beta)”

  5. Run fdroid build --latest com.fsck.k9 to build the project using F-Droid’s toolchain.

  6. Push the changes to your fork of the fdroiddata repository.

  7. Open a merge request on Gitlab. (The message from the server after the push in the previous step should contain a URL)

  8. Select the App update template and fill it out.

  9. Create merge request and the F-Droid team will do the rest.

Create release on Google Play

  1. Go to the Google Play Console
  2. Select the K-9 Mail app
  3. Click on Open testing in the left sidebar
  4. Click on Create new release
  5. Upload the APK to App bundles
  6. Fill out Release name (e.g. “$versionCode ($versionName)”)
  7. Fill out Release notes (copy from app-metadata/com.fsck.k9/en-US/changelogs/${versionCode}.txt)
  8. Click Next
  9. Review the release
  10. Configure a full rollout for beta versions
  11. On the Publishing overview page, click Send change for review
  12. Wait for the review to complete
  13. In case of a rejection, fix the issues and repeat the process

Release a stable version

When the team decides the main branch is stable enough and it’s time to release a new stable version, create a new maintenance branch (off main) using the desired version number with the last two digits dropped followed by -MAINT. Example: 6.8-MAINT when the first stable release is K-9 Mail 6.800.

Ideally the first stable release contains no code changes when compared to the last beta version built from main. That way the new release won’t contain any changes that weren’t exposed to user testing in a beta version before.

  1. Switch to the appropriate maintenance branch, e.g. 6.8-MAINT

  2. Update versionCode and versionName in app-k9mail/build.gradle.kts (stable releases use an even digit after the dot, e.g. 5.400, 6.603)

  3. Create change log entries in

    • app-k9mail/src/main/res/raw/changelog_master.xml
    • app-k9mail/fastlane/metadata/android/en-US/changelogs/${versionCode}.txt Use past tense. Try to keep them high level. Focus on the user (experience).
  4. Update the metadata link to point to K-9 Mail’s data: ln --symbolic --no-dereference --force app-metadata/com.fsck.k9 metadata

  5. Commit the changes. Message: “Version $versionName”

  6. Run ./gradlew clean :app-k9mail:assembleRelease --no-build-cache --no-configuration-cache

  7. Update an existing installation to make sure the app is signed with the proper key and runs on a real device.

    adb install -r app-k9mail/build/outputs/apk/release/app-k9mail-release.apk
    
  8. Tag as $versionName, e.g. 6.800

  9. Copy app-k9mail/build/outputs/apk/release/app-k9mail-release.apk as k9-${versionName}.apk to Google Drive (MZLA Team > K9 > APKs)

  10. Update gh-pages branch with the new change log. Create a new file if it’s the first stable release in a series.

  11. Push maintenance branch

  12. Push tags

  13. Push gh-pages branch

Create release on GitHub

  1. Go to https://github.com/thunderbird/thunderbird-android/tags and select the appropriate tag
  2. Click “Create release from tag”
  3. Fill out the form
    • Click “Generate release notes”
    • Replace contents under “What’s changed” with change log entries
    • Add GitHub handles in parentheses to change log entries
    • If necessary, add another entry “Internal changes” (or similar) so people who contributed changes outside of the entries mentioned in the change log can be mentioned via GitHub handle.
    • Attach the APK
    • Select “Set as the latest release”
    • Click “Publish release”

Create release on F-Droid

  1. Fetch the latest changes from the fdroiddata repository.

  2. Switch to a new branch in your copy of the fdroiddata repository.

  3. Edit metadata/com.fsck.k9.yml to create a new entry for the version you want to release. Usually it’s copy & paste of the previous entry and adjusting versionName, versionCode, and commit (use the tag name). Change CurrentVersion and CurrentVersionCode to the new values, making this the new stable/recommended build.

    Example:

    - versionName: "${versionName}"
      versionCode: ${versionCode}
      commit: "${tagName}"
      subdir: app-k9mail
      gradle:
        - yes
      scandelete:
        - build-plugin/build
    
  4. Commit the changes. Message: “Update K-9 Mail to $newVersionName”

  5. Run fdroid build --latest com.fsck.k9 to build the project using F-Droid’s toolchain.

  6. Push the changes to your fork of the fdroiddata repository.

  7. Open a merge request on Gitlab. (The message from the server after the push in the previous step should contain a URL)

  8. Select the App update template and fill it out.

  9. Create merge request and the F-Droid team will do the rest.

Create release on Google Play

  1. Go to the Google Play Console
  2. Select the K-9 Mail app
  3. Click on Production in the left sidebar
  4. Click on Create new release
  5. Upload the APK to App bundles
  6. Fill out Release name (e.g. “$versionCode ($versionName)”)
  7. Fill out Release notes (copy from app-k9mail/fastlane/metadata/android/en-US/changelogs/${versionCode}.txt)
  8. Click Next
  9. Review the release
  10. Start with a staged rollout (usually 20%)
  11. On the Publishing overview page, click Send change for review
  12. Wait for the review to complete
  13. In case of a rejection, fix the issues and repeat the process
  14. Once the review is complete, monitor the staged rollout for issues and increase the rollout percentage as necessary

Troubleshooting

F-Droid

If the app doesn’t show up in the F-Droid client:

  • Check the build cycle, maybe you just missed it and it will be available in the next cycle. (The cycle is usually every 5 days.)
  • Check F-Droid Status for any issues.
  • Check F-Droid Monitor for any errors mentioning com.fsck.k9.
Last change: 2025-05-13, commit: eee090c

Managing strings

We use Android’s resource system to localize user-visible strings in our apps.

Our source language is English (American English to be more precise, but simply “English” (en) on Weblate).

Translations of source strings happen exclusively in our Weblate project. This means the source language is only modified by changes to this repository, i.e. via pull requests. Translations are only updated on Weblate and then merged into this repository by the Thunderbird team. This is to avoid overlapping changes in both repositories that will lead to merge conflicts.

Adding a string

Add a new string to the appropriate res/values/strings.xml file.

Please don’t add any translations for this new string to this repository. If you can also provide a translation for the new string, wait until the change is merged into this repository and propagated to Weblate. Then translate the new string on Weblate.

Changing a string

Changing a string should be avoided. Weblate doesn’t automatically invalidate translations when a source string is changed. This can be worked around by removing the old string and adding a new one. Make sure to only modify the source language. It’s fine for the translations to then contain unused strings. The next merge with Weblate will remove those.

Removing a string

Remove the source string from res/values/strings.xml. Don’t modify translations under res/values-<lang>/strings.xml. The next merge from Weblate will automatically get rid of the translated strings.

Changing translations in this repository

This should be avoided whenever possible, as it can create merge conflicts between Weblate and this repository. If you need to change individual strings, please translate them on Weblate instead. If a mechanical change is necessary across all languages, this should be discussed with the core team who will use this procedure:

  1. Lock all components on Weblate by clicking the “Lock” button in the repository maintenance screen.
  2. Commit all outstanding changes by clicking the “Commit” button in the same screen.
  3. Trigger creating a pull request containing translation updates from Weblate by clicking the “Push” button in the repository maintenance screen.
  4. Merge that pull request containing updates from Weblate into this repository.
  5. Create a pull request to change the translated files, following the established procedures to get it merged. Make sure you’ve rebased against the latest changes.
  6. Wait for the changes in this repository to be automatically propagated to and processed by Weblate.
  7. Unlock components on Weblate by clicking the “Unlock” button in the repository maintenance screen.

Managing translations

Right now we’re using the androidResources.localeFilters mechanism provided by the Android Gradle Plugin to limit which languages are included in builds of the app, See localFilters.

This list needs to be kept in sync with the string array supported_languages, so the in-app language picker offers exactly the languages that are included in the app.

Removing a language

  1. Remove the language code from the androidResources.localeFilters list in app-thunderbird/build.gradle.kts and app-k9mail/build.gradle.kts.
  2. Remove the entry from supported_languages in app/core/src/main/res/values/arrays_general_settings_values.xml.

Adding a language

  1. Add the language code to the androidResources.localeFilters list in app-thunderbird/build.gradle.kts and app-k9mail/build.gradle.kts.
  2. Add an entry to supported_languages in app/core/src/main/res/values/arrays_general_settings_values.xml.
  3. Make sure that language_values in app/core/src/main/res/values/arrays_general_settings_values.xml contains an entry for the language code you just added. If not:
    1. Add the language name (in its native script) to language_entries in app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml. Please note that this list should be ordered using the Unicode default collation order.
    2. Add the language code to language_values in app/core/src/main/res/values/arrays_general_settings_values.xml so that the index in the list matches that of the newly added entry in language_entries.

Adding a component on Weblate

When adding a new code module that is including translatable strings, a new components needs to be added to Weblate.

  1. Go the the Weblate page to add a component.
  2. Switch to the “From existing component” tab.
  3. Enter a name for the component.
  4. For “Component”, select “K-9 Mail/Thunderbird/ui-legacy”.
  5. Press the “Continue” button.
  6. Under “Choose translation files to import”, select “Specify configuration manually”.
  7. Press the “Continue” button.
  8. For “File format”, select “Android String Resource”.
  9. Under “File mask”, enter the path to the string resource files with a wildcard, e.g. feature/account/common/src/main/res/values-*/strings.xml.
  10. Under “Monolingual base language file”, enter the path to the string source file, e.g. feature/account/common/src/main/res/values/strings.xml.
  11. Uncheck “Edit base file”.
  12. For “Translation license”, select “Apache License 2.0”.
  13. Press the “Save” button.

Things to note

For some languages Android uses different language codes than typical translation tools, e.g. Hebrew’s code is he on Weblate, but iw on Android. When writing automation tools, there needs to be a mapping step involved.

See translation-cli for an example.

Last change: 2025-05-13, commit: eee090c

Java to Kotlin Conversion Guide

This guide describes our process for converting Java code to Kotlin.

Why Convert to Kotlin?

Java and Kotlin are compatible languages, but we decided to convert our codebase to Kotlin for the following reasons:

  • Kotlin is more concise and expressive than Java.
  • Kotlin has better support for null safety.
  • Kotlin has a number of modern language features that make it easier to write maintainable code.

See our ADR-0001 for more information.

How to Convert Java Code to Kotlin

  1. Write tests for any code that is not adequately covered by tests.
  2. Use the “Convert Java File to Kotlin File” action in IntelliJ or Android Studio to convert the Java code.
  3. Fix any issues that prevent the code from compiling after the automatic conversion.
  4. Commit the changes as separate commits:
    1. The change of file extension (e.g. example.java -> example.kt).
    2. The conversion of the Java file to Kotlin.
    • This can be automated by IntelliJ/Android Studio if you use their VCS integration and enable the option to commit changes separately.
  5. Refactor the code to improve readability and maintainability. This includes:
    1. Removing unnecessary code.
    2. Using Kotlin’s standard library functions, language features, null safety and coding conventions.

Additional Tips

  • Use when expressions instead of if-else statements.
  • Use apply and also to perform side effects on objects.
  • Use @JvmField to expose a Kotlin property as a field in Java.

Resources

Last change: 2025-05-13, commit: eee090c

Git Commit Guide

Use Conventional Commits to write consistent and meaningful commit messages. This makes your work easier to review, track, and maintain for everyone involved in the project.

✍️ Commit Message Format

<type>(<scope>): <description>

<body>

<footer(s)>

Components:

  • <type>: The type of change being made (e.g., feat, fix, docs).
  • <scope> (optional): The scope indicates the area of the codebase affected by the change (e.g., auth, ui).
  • <description>: Short description of the change (50 characters or less)
  • <body> (optional): Explain what changed and why, include context if helpful.
  • <footer(s)> (optional): Include issue references, breaking changes, etc.

Examples

Basic:

feat: add QR code scanner

With scope:

feat(auth): add login functionality

With body and issue reference:

fix(api): handle null response from login endpoint

Checks for missing tokens to prevent app crash during login.

Fixes #123

🏷️ Commit Types

TypeUse for…Example
featNew featuresfeat(camera): add zoom support
fixBug fixesfix(auth): handle empty username crash
docsDocumentation onlydocs(readme): update setup instructions
styleCode style (no logic changes)style: reformat settings screen
refactorCode changes (no features/fixes)refactor(nav): simplify stack setup
testAdding/editing teststest(api): add unit test for login
choreTooling, CI, dependencieschore(ci): update GitHub Actions config
revertReverting previous commitsrevert: remove feature flag

📍Optional Scope

The scope is optional but recommended for clarity, especially for large changes or or when multiple areas of the codebase are involved.

ScopeUse for…Example
authAuthenticationfeat(auth): add login functionality
settingsUser settingsfeat(settings): add dark mode toggle
buildBuild systemfix(build): improve build performance
uiUI/themerefactor(ui): split theme into modules
depsDependencieschore(deps): bump Kotlin to 2.0.0

🧠 Best Practices

1. One Commit, One Purpose

  • ✅ Each commit should represent a single logical change or addition to the codebase.
  • ❌ Don’t mix unrelated changes together (e.g., fixing a bug and updating docs, or changing a style and ) adding a feature).

2. Keep It Manageable

  • ✅ Break up large changes into smaller, more manageable commits.
  • ✅ If a commit changes more than 200 lines of code, consider breaking it up.
  • ❌ Avoid massive, hard-to-review commits.

3. Keep It Working

  • ✅ Each commit should leave the codebase in a buildable and testable state.
  • ❌ Never commit broken code or failing tests.

4. Think About Reviewers (and Future You)

  • ✅ Write messages for your teammates and future self, assuming they have no context.
  • ✅ Explain non-obvious changes or decisions in the message body.
  • ✅ Consider the commit as a documentation tool.
  • ❌ Avoid jargon, acronyms, or vague messages like update stuff.

Summary

  • Use Conventional Commits for consistency.
  • Keep commit messages short, structured, and focused.
  • Make each commit purposeful and self-contained.
  • Write commits that make collaboration and future development easier for everyone—including you.
Last change: 2025-05-13, commit: eee090c

Architecture Decision Records

The docs/architecture/adr folder contains the architecture decision records (ADRs) for our project.

ADRs are short text documents that serve as a historical context for the architecture decisions we make over the course of the project.

What is an ADR?

An Architecture Decision Record (ADR) is a document that captures an important architectural decision made along with its context and consequences. ADRs record the decision making process and allow others to understand the rationale behind decisions, providing insight and facilitating future decision-making processes.

Format of an ADR

We adhere to Michael Nygard’s ADR format proposal, where each ADR document should contain:

  1. Title: A short descriptive name for the decision.
    1. Link to Issue: A link to the issue that prompted the decision.
    2. Link to Pull Request: A link to the pull request that implements the ADR.
    3. Link to Tracking Issue: A link to the tracking issue, if applicable.
  2. Status: The current status of the decision (proposed, accepted, rejected, deprecated, superseded)
  3. Context: The context that motivates this decision.
  4. Decision: The change that we’re proposing and/or doing.
  5. Consequences: What becomes easier or more difficult to do and any risks introduced as a result of the decision.

Creating a new ADR

When creating a new ADR, please follow the provided ADR template file and ensure that your document is clear and concise.

Once you are ready to propose your ADR, you should:

  1. Create an issue in the repository, get consensus from at least one other project contributor.
  2. Make a post on the mobile-planning list to announce your ADR. You can use the below template as needed.
  3. Create a pull request in the repository linking the issue.
  4. Make a decision together with mobile module owners, the PR will be merged when accepted.

Directory Structure

The ADRs will be stored in a directory named docs/adr, and each ADR will be a file named NNNN-title-with-dashes.md where NNNN is a four-digit number that is increased by 1 for every new adr.

ADR Life Cycle

The life cycle of an ADR is as follows:

  1. Proposed: The ADR is under consideration.
  2. Accepted: The decision described in the ADR has been accepted and should be adhered to, unless it is superseded by another ADR.
  3. Rejected: The decision described in the ADR has been rejected.
  4. Deprecated: The decision described in the ADR is no longer relevant due to changes in system context.
  5. Superseded: The decision described in the ADR has been replaced by another decision.

Each ADR will have a status indicating its current life-cycle stage. An ADR can be updated over time, either to change the status or to add more information.

Contributions

We welcome contributions in the form of new ADRs or updates to existing ones. Please ensure all contributions follow the standard format and provide clear and concise information.

Appendix: Intent to Adopt Template

You may use this template in your Intent to Adopt email as noted above. Tweak it as you feel is useful.

Hello everyone,

I’m writing to share an intent to adopt a new architecture decision: [ADR-[Number]] [Title of ADR]

This change addresses [brief summary of the problem] and proposes [brief description of the approach].

This decision is based on [briefly mention motivating factors, constraints, or technical context].

You can read the full proposal here: [link to ADR]

If you have feedback or concerns, please respond in the linked issue. We plan to finalize the decision after [proposed date], factoring in discussion at that time.

Thanks, [Your Name]

Switch from Java to Kotlin

Status

  • Accepted

Context

We’ve been using Java as our primary language for Android development. While Java has served us well, it has certain limitations in terms of null safety, verbosity, functional programming, and more. Kotlin, officially supported by Google for Android development, offers solutions to many of these issues and provides more modern language features that can improve productivity, maintainability, and overall code quality.

Decision

Switch our primary programming language for Android development from Java to Kotlin. This will involve rewriting our existing Java codebase in Kotlin and writing all new code in Kotlin. To facilitate the transition, we will gradually refactor our existing Java codebase to Kotlin.

Consequences

  • Positive Consequences
    • Improved null safety, reducing potential for null pointer exceptions.
    • Increased code readability and maintainability due to less verbose syntax.
    • Availability of modern language features such as coroutines for asynchronous programming, and extension functions.
    • Officially supported by Google for Android development, ensuring future-proof development.
  • Negative Consequences
    • The process of refactoring existing Java code to Kotlin can be time-consuming.
    • Potential for introduction of new bugs during refactoring.
Last change: 2025-05-13, commit: eee090c

UI - Wrap Material Components in Atomic Design System

Status

  • Accepted

Context

As we continued developing our Jetpack Compose application, we found a need to increase the consistency, reusability, and maintainability of our user interface (UI) components. We have been using Material components directly throughout our application. This lead to a lack of uniformity and increases the complexity of changes as the same modifications had to be implemented multiple times across different screens.

Decision

To address these challenges, we’ve decided to adopt an Atomic Design System as a foundation for our application UI. This system encapsulates Material components within our own components, organized into categories of atoms, molecules, and organisms. We also defined templates as layout structures that can be flexibly combined to construct pages. These components collectively form the building blocks that we are using to construct our application’s UI.

Consequences

  • Positive Consequences
    • Increased reusability of components across the application, reducing code duplication.
    • More consistent UI and uniform styling across the entire application.
    • Improved maintainability, as changes to a component only need to be made in one place.
  • Negative Consequences
    • Initial effort and time investment needed to implement the atomic design system.
    • Developers need to adapt to the new system and learn how to use it effectively.
    • Potential for over-complication if simple components are excessively broken down into atomic parts.
Last change: 2025-05-13, commit: eee090c

Switch Test Assertions from Truth to assertk

Status

  • Accepted

Context

Our project has been using the Truth testing library for writing tests. While Truth has served us well, it is primarily designed for Java and lacks some features that make our Kotlin tests more idiomatic and expressive. As our codebase is primarily Kotlin, we have been looking for a testing library that is more aligned with Kotlin’s features and idioms.

Decision

We have decided to use assertk as the default assertions framework for writing tests in our project. assertk provides a fluent API that is very similar to Truth, making the transition easier. Moreover, it is designed to work well with Kotlin, enabling us to leverage Kotlin-specific features in our tests.

We’ve further committed to converting all pre-existing tests from Truth to assertk.

Consequences

Note: The migration of all Truth tests to assertk has already been completed.

  • Positive Consequences
    • Ease of Transition: The syntax of assertk is very similar to Truth, which makes the migration process smoother.
    • Kotlin-Friendly: assertk is designed specifically for Kotlin, allowing us to write more idiomatic and expressive Kotlin tests.
  • Negative Consequences
    • Dependency: While we are replacing one library with another, introducing a new library always carries the risk of bugs or future deprecation.
    • Migration Effort: Existing tests written using Truth will need to be migrated to use assertk, requiring some effort, although mitigated by the similar syntax.
Last change: 2025-05-13, commit: eee090c

Naming Conventions for Interfaces and Their Implementations

Status

  • Accepted

Context

When there’s an interface that has multiple implementations it’s often easy enough to give meaningful names to both the interface and the implementations (e.g. the interface Backend with the implementations ImapBackend and Pop3Backend). Naming becomes harder when the interface mainly exists to allow having isolated unit tests and the production code contains exactly one implementation of the interface. Prior to this ADR we didn’t have any naming guidelines and the names varied widely. Often when there was only one (production) implementation, the class name used one of the prefixes Default, Real, or K9. None of these had any special meaning and it wasn’t clear which one to pick when creating a new interface/class pair.

Decision

We’ll be using the following guidelines for naming interfaces and their implementation classes:

  1. Interface Naming: Name interfaces as if they were classes, using a clear and descriptive name. Avoid using the “IInterface” pattern.
  2. Implementation Naming: Use a prefix that clearly indicates the relationship between the interface and implementation, such as DatabaseMessageStore or InMemoryMessageStore for the MessageStore interface.
  3. Descriptive Names: Use descriptive names for interfaces and implementing classes that accurately reflect their purpose and functionality.
  4. Platform-specific Implementations: Use the platform name as a prefix for interface implementations specific to that platform, e.g. AndroidPowerManager.
  5. App-specific Implementations: Use the prefix K9 for K-9 Mail and Tb for Thunderbird when app-specific implementations are needed, e.g. K9AppNameProvider and TbAppNameProvider.
  6. Flexibility: If no brief descriptive name fits and there is only one production implementation, use the prefix Default, like DefaultImapFolder.

Consequences

  • Positive Consequences
    • Improved code readability and maintainability through consistent naming.
    • Reduced confusion and misunderstandings by using clear and descriptive names.
  • Negative Consequences
    • Initial effort is required to rename existing classes that do not follow these naming conventions.
Last change: 2025-05-13, commit: eee090c

Central Management of Android Project Dependencies and Gradle Configurations via Build-Plugin Module

Status

  • Accepted

Context

In our Android project, managing dependencies and configurations directly within each module’s build.gradle.kts file has historically led to inconsistencies, duplication, and difficulty in updates. This challenge was particularly noticeable when maintaining the project configuration. By centralizing this setup in a build-plugin module, we can encapsulate and reuse Gradle logic, streamline the build process, and ensure consistency across all project modules and ease maintainability of our codebase.

Decision

To address these challenges, we have decided to establish a build-plugin module within our project. This module will serve as the foundation for all common Gradle configurations, dependency management, and custom plugins, allowing for simplified configuration across various project modules and plugins. Key components of this module include:

  • Custom Plugins: A suite of custom plugins that configure Gradle for different project aspects, ensuring each project type has tailored and streamlined build processes. These plugins should cover Android application, Android library, Jetpack Compose and Java modules.
  • Dependency Management: Utilizing the Gradle Version Catalog to centrally manage and update all dependencies and plugins, ensuring that every module uses the same versions and reduces the risk of conflicts.
  • Common Configuration Settings: Establishing common configurations for Java, Kotlin, and Android to reduce the complexity and variability in setup across different modules.

Consequences

Positive Consequences

  1. Consistency Across Modules: All project modules will use the same versions of dependencies and plugins, reducing the risk of conflicts and enhancing uniformity. They will also share common configurations, ensuring consistency in the build process.
  2. Ease of Maintenance: Centralizing dependency versions in the Gradle Version Catalog allows for simple and quick updates to libraries and tools across all project modules from a single source.
  3. Simplified Configuration Process: The custom plugins within the build-plugin module provides a streamlined way to apply settings and dependencies uniformly, enhancing productivity and reducing setup complexity.

Negative Consequences

  1. Initial Overhead: The setup of the build-plugin module with a Gradle Version Catalog and the migration of existing configurations required an initial investment of time and resources, but this has been completed.
  2. Complexity for New Developers: The centralized build architecture, particularly with the use of a Gradle Version Catalog, may initially seem daunting to new team members who are unfamiliar with this level of abstraction.
  3. Dependency on the Build-Plugin Module: The entire project becomes reliant on the stability and accuracy of the build-plugin module. Errors within this module or the catalog could impact the build process across all modules.
Last change: 2025-05-13, commit: eee090c

White Label Architecture

Status

  • Accepted

Context

Our project hosts two separate applications, K-9 Mail and Thunderbird for Android, which share a significant amount of functionality. Despite their common features, each app requires distinct branding elements such as app names, themes, and specific strings.

Decision

We have decided to adopt a modular white-label architecture, where each application is developed as a separate module that relies on a shared codebase. This structure allows us to streamline configuration details specific to each brand either during build or at runtime. This is how we structure the modules:

Application Modules

There will be 2 separate modules for each of the two applications: Thunderbird for Android will be located in app-thunderbird and K-9 Mail in app-k9mail. These modules will contain app-specific implementations, configurations, resources, and startup logic. They should solely depend on the app-common module for shared functionalities and may selectively integrate other modules when needed to configure app-specific functionality.

App Common Module

A central module named app-common acts as the central integration point for shared code among the applications. This module contains the core functionality, shared resources, and configurations that are common to both apps. It should be kept as lean as possible to avoid unnecessary dependencies and ensure that it remains focused on shared functionality.

Consequences

Positive Consequences

  • Enhanced maintainability due to a shared codebase for common functionalities, reducing code duplication.
  • Increased agility in developing and deploying new features across both applications, as common enhancements need to be implemented only once.

Negative Consequences

  • Potential for configuration complexities as differentiations increase between the two applications.
  • Higher initial setup time and learning curve for new developers due to the modular and decoupled architecture.
Last change: 2025-05-13, commit: eee090c

Project Structure

Status

  • Accepted

Context

The project consists of two distinct applications. To improve maintainability and streamline development, we propose a modular structure using Gradle. This structure is designed to enable clear separation of concerns, facilitate scalable growth, and ensure efficient dependency management. It consists of various module types such as app, app-common, feature, core, and library modules, promoting enhanced modular reusability.

Decision

To achieve the goals outlined in the context, we have decided to adopt the following modular structure:

  1. App Modules:
    • app-thunderbird and app-k9mail are the modules for the two applications, Thunderbird for Android and K-9 Mail respectively. These modules will contain app-specific implementations, configurations, resources, and startup logic. They should solely depend on the app-common module for shared functionalities and may selectively integrate feature and core to setup app-specific needs.
  2. App Common Module:
    • app-common: Acts as the central hub for shared code between both applications. This module serves as the primary “glue” that binds various feature modules together, providing a seamless integration point. While it can depend on library modules for additional functionalities, its main purpose is to orchestrate the interactions among the feature and core modules, ensuring similar functionality across both applications. This module should be kept lean to avoid unnecessary dependencies and ensure it remains focused on shared functionality.
  3. Feature Modules:
    • feature:*: These are independent feature modules, that encapsulate distinct user-facing features. They are designed to be reusable and can be integrated into any application module as needed. They maintain dependencies on core modules and may interact with other feature or library modules.
  4. Core Module:
    • core:*: The core modules contain essential utilities and base classes used across the entire project. These modules are grouped by their functionality (e.g., networking, database management, theming, common utilities). This segmentation allows for cleaner dependency management and specialization within foundational aspects.
  5. Library Modules:
    • library:* These modules are for specific implementations that might be used across various features or applications. They could be third-party integrations or complex utilities and eventually shared across multiple projects.
graph TD
    subgraph APP[App]
        APP_K9["`
            **:app-k9mail**
            K-9 Mail
        `"]
        APP_TB["`
            **:app-thunderbird**
            Thunderbird for Android
        `"]
    end

    subgraph COMMON[App Common]
        APP_COMMON["`
            **:app-common**
            Integration Code
        `"]
    end

    subgraph FEATURE[Feature]
        FEATURE1[Feature 1]
        FEATURE2[Feature 2]
    end

    subgraph CORE[Core]
        CORE1[Core 1]
        CORE2[Core 2]
    end

    subgraph LIBRARY[Library]
        LIB1[Library 1]
        LIB2[Library 2]
    end

    APP --> |depends on| COMMON
    COMMON --> |integrates| FEATURE
    FEATURE --> |uses| CORE
    FEATURE --> |uses| LIBRARY

    classDef module fill:yellow
    classDef app fill:azure
    classDef app_common fill:#ddd
    class APP_K9 app
    class APP_TB app
    class APP_COMMON app_common

Legacy Modules

Modules that are still required for the project to function, but don’t follow the new project structure.

These modules should not be used for new development.

The goal is to migrate the functionality of these modules to the new structure over time. By placing them under the legacy module, we can easily identify and manage them.

graph TD
    subgraph APP[App]
        APP_K9["`
            **:app-k9mail**
            K-9 Mail
        `"]
        APP_TB["`
            **:app-thunderbird**
            Thunderbird for Android
        `"]
    end

    subgraph COMMON[App Common]
        APP_COMMON["`
            **:app-common**
            Integration Code
        `"]
    end

    subgraph FEATURE[Feature]
        FEATURE1[Feature 1]
        FEATURE2[Feature 2]
        FEATURE3[Feature from Legacy]
    end

    subgraph CORE[Core]
        CORE1[Core 1]
        CORE2[Core 2]
        CORE3[Core from Legacy]
    end

    subgraph LIBRARY[Library]
        LIB1[Library 1]
        LIB2[Library 2]
    end

    APP --> |depends on| COMMON
    COMMON --> |integrates| FEATURE
    FEATURE --> |uses| CORE
    FEATURE --> |uses| LIBRARY

    subgraph LEGACY[Legacy]
        LEG[Legacy Code]
    end

    COMMON -.-> |integrates| LEGACY
    LEG -.-> |migrate to| FEATURE3
    LEG -.-> |migrate to| CORE3

    classDef module fill:yellow
    classDef app fill:azure
    classDef app_common fill:#ddd
    classDef legacy fill:#F99
    class APP_K9 app
    class APP_TB app
    class APP_COMMON app_common
    class LEGACY legacy

Consequences

Positive Consequences

  • Improved modularity facilitates easier code maintenance and scaling.
  • Clear separation of concerns reduces dependencies and potential conflicts between modules.
  • Enhanced reusability of the feature, core and library modules across different parts of the application or even in different projects.

Negative Consequences

  • Initial complexity in setting up and managing multiple modules may increase the learning curve and setup time for new developers.
  • Over-modularization can lead to excessive abstraction, potentially impacting runtime performance and complicating the debugging process.
  • Legacy modules may require additional effort to migrate to the new structure, potentially causing delays in the adoption of the new architecture.
Last change: 2025-05-13, commit: eee090c

Change Shared Modules package to net.thunderbird

Status

  • Accepted

Context

The Thunderbird Android project is a white-label version of K-9 Mail, and both apps — app-thunderbird and app-kmail — coexist in the same repository. They have distinct application IDs and branding, but share a significant portion of the code through common modules.

These shared modules currently use the app.k9mail or com.fsck package name, which are legacy artifacts from K-9 Mail. While K-9 will remain available for some time, the project’s primary focus has shifted toward Thunderbird.

To reflect this shift, establish clearer ownership, and prepare for future development (including cross-platform code integration), we will rename the packages in shared modules from app.k9mail and com.fsck to net.thunderbird. The actual application IDs and package names of app-thunderbird and app-k9mail must remain unchanged.

Decision

We decided to rename the base package in all shared modules from app.k9mail and com.fsck to net.thunderbird.

Specifically:

  • All Kotlin/Java packages in shared modules will be refactored to use net.thunderbird as the base
  • This must not affect the application IDs or packages of app-thunderbird or app-kmail, which will remain as-is
  • All references, imports, and configuration references will be updated accordingly
  • Tests, resources, and Gradle module settings will be adjusted to match the new package structure

This change will establish a clearer identity for the shared code, align with Thunderbird’s branding, and prepare the project for cross-platform development.

Consequences

Positive Consequences

  • Shared code reflects Thunderbird branding and identity
  • Reduces confusion when navigating codebase shared by both apps
  • Sets the foundation for cross-platform compatibility and future modularization
  • Helps reinforce long-term direction of the project toward Thunderbird

Negative Consequences

  • Large-scale refactoring required across multiple modules
  • Risk of introducing regressions during package renaming
  • Potential for disruption in local development setups (e.g., IDE caching, broken imports)
  • Contributors familiar with the old structure may need time to adjust
Last change: 2025-05-13, commit: eee090c

How to Document

This guide provides detailed instructions for contributing to and maintaining the documentation for the Thunderbird for Android project. It explains the tools used, the structure of the documentation, and guidelines for creating and editing content.

We use mdbook to generate the documentation. The source files for the documentation are located in the docs/ directory.

Contributing

If you’d like to contribute to this project, please familiarize yourself with our Contribution Guide.

To add or modify the documentation, please edit the markdown files located in the docs/ directory using standard Markdown syntax, including GitHub flavored Markdown. You can use headers, lists, links, code blocks, and other Markdown features to structure your content.

For creating diagrams, we use the mermaid syntax. To include mermaid diagrams in your Markdown files, use the following syntax:

```mermaid
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
```

Result:

graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;

Adding a New Page

To add a new page, create a markdown file in the docs/ directory or within a suitable subfolder. For example:

  • To create a new top-level page: docs/NEW_PAGE.md.
  • To create a page within a subfolder: docs/subfolder/new-subpage.md.

To include the new page in the table of contents, add a link to the SUMMARY.md file pointing to newly created page.

For consistency with GitHub conventions and other mandatory files, markdown files in the top level docs/ directory shall be written in uppercase, as well the README.md file within subfolders. Further markdown files in subdirectories shall use a lowercase filename.

Organizing with Subfolders

Subfolders in the docs/ folder can be used to organize related documentation. This can be useful if related topics should be grouped together. For example, we have a subfolder named architecture/ for all documentation related to our application’s architecture.

Linking New Pages in the Summary

The SUMMARY.md file serves as the table of contents (TOC) for the documentation. To include the new page in the TOC, a link needs to be added in the SUMMARY.md file, like so:

- [Page Title](relative/path/to/file.md)

Indentation is used to create hierarchy in the TOC:

- [Page Title](relative/path/to/file.md)
  - [Subpage Title](relative/path/to/subfolder/file.md)

Assets

If you need to embed images, put them in the assets folder closest to the file they are being used in. This can either be the top-level assets folder, or a (potentially new) assets subfolder in the respective section.

Documentation Toolchain

The documentation is built using mdbook and several extensions. Follow these steps to set up the required tools.

Install mdbook and extensions

Ensure you have Cargo installed, then run:

./docs/install.sh

This script installs mdbook and the required extensions and other dependencies.

Use –force to update the dependencies, recommended when mdbook was updated:

./docs/install.sh --force

Extensions

We use the following mdbook extensions:

Building the Documentation

Once you have mdbook and its extensions installed, you can build the documentation by running this command:

mdbook build docs

The generated documentation will be available in the book/docs/latest/ folder.

To preview the documentation, run the following command:

mdbook serve docs --open

The mdbook serve docs command will serve the book at http://localhost:3000 and rebuild the documentation on changes. The --open option will open the book in your web browser and is optional.

Last change: 2025-05-13, commit: eee090c