Building Tuta mail app for Android on Ubuntu
These are the steps I followed to build my own copy of the Tuta Mail Android app on Ubuntu. I’d initially done this to try to fix the app going offline after 10–15 minutes, but I wasn’t successful. I’m leaving this here in case anyone else runs into problems building. I gave up trying to do it on Windows in the end, as I couldn’t update Node.js or another build tool (make.exe?) to the required version.
The official build instructions live in the repository’s BUILDING.md file, but they skip a few system packages and all of the environment/path variables the build actually needs. This post fills in the gaps, based on the troubleshooting I did to get a successful build.
Note: a self-built app does not update itself. If you want automatic updates, use the app from the Google Play Store or F-Droid, or the APK published on the GitHub releases page.
Prerequisites
The official docs only list Git, Node.js and the Android SDK (Software Development Kit). The build also needs the standard build toolchain, Java 17, Rust and Emscripten.
System packages
sudo apt update
sudo apt install build-essential git
build-essential— the compiler/make toolchain used to build the native (WebAssembly (WASM) and C) dependencies.git— to clone the repository and its submodules.
Node.js
Install the current Node.js from the NodeSource repository (the version
required is noted in the repo’s package.json engines field):
sudo apt install ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=24
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install nodejs
sudo npm install -g npm@latest
Java 17
The Android Gradle build needs a JDK (Java Development Kit); 17 is known to work:
sudo apt install -y openjdk-17-jdk
java -version
javac -version
Rust
Rust (1.80 or newer) is used for the WASM crypto code:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup target add wasm32-unknown-unknown
cargo --version
Emscripten
Emscripten compiles the C/C++ crypto libraries to WebAssembly:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
emcc --version
echo 'source "$HOME/emsdk/emsdk_env.sh"' >> ~/.bashrc
cd ..
Android SDK and NDK
Install the Android command line tools, then use sdkmanager to pull in the
platform tools and the NDK (Native Development Kit).
mkdir -p ~/android-sdk/cmdline-tools
cd ~/android-sdk/cmdline-tools
wget https://dl.google.com/android/repository/commandlinetools-linux-15859902_latest.zip
unzip commandlinetools-linux-15859902_latest.zip
mv cmdline-tools latest
cd ~
The zip build number changes over time; if the above URL is stale, grab the current Linux command line tools from the Android developer downloads page.
Accept the licences and install the platform tools:
yes | sdkmanager --licenses
sdkmanager "platform-tools"
NDK version
The exact NDK version the app needs is printed in the build output, so the easiest approach is to run the build once and read the version it complains about, then install that one:
sdkmanager "ndk;27.3.13750724"
If you’d rather look it up yourself, the NDK versions are listed on the NDK downloads page (under the LTS releases).
Path variables
These are the variables that aren’t mentioned in the official docs. Add them
to ~/.bashrc so they survive a reboot:
export ANDROID_HOME="$HOME/android-sdk"
export ANDROID_SDK_ROOT="$HOME/android-sdk"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"
Then reload your shell:
source ~/.bashrc
which sdkmanager
which adb
If the Gradle build still can’t find the SDK, also add the variables system wide:
echo 'ANDROID_HOME="/home/luke/android-sdk"' | sudo tee -a /etc/environment
echo 'ANDROID_SDK_ROOT="/home/luke/android-sdk"' | sudo tee -a /etc/environment
source /etc/environment
The app’s Gradle build also expects a local.properties file pointing at the
SDK. Create it inside the repo once it’s cloned:
echo "sdk.dir=$HOME/android-sdk" > /path/to/tutanota/app-android/local.properties
Building the app
Clone the repository and check out the latest Android release tag (replace
xxx with the current tag, e.g. tutanota-android-release-123.456.789):
git clone https://github.com/tutao/tutanota.git
cd tutanota
git checkout tutanota-android-release-xxx
Initialise and fetch the submodules:
git submodule init
git submodule sync --recursive
git submodule update
Install the dependencies:
npm ci
Keystore
The signing keystore must be placed in the app directory
(app-android/app/) for the build to find it. If you don’t have one yet,
create it:
keytool -genkey -noprompt -keystore MyKeystore.jks -alias tutaKey -keyalg RSA -keysize 2048 -validity 10000 -deststoretype pkcs12 -storepass CHANGEME -keypass CHANGEME -dname "CN=com.example"
Then copy the keystore into the app directory:
cp MyKeystore.jks app-android/app/
Run the build
APK_SIGN_ALIAS="tutaKey" APK_SIGN_STORE='MyKeystore.jks' APK_SIGN_STORE_PASS="CHANGEME" APK_SIGN_KEY_PASS="CHANGEME" node android
The build prints the path of the produced APK (Android Package) when it finishes.
Note: run the whole command above, including the APK_SIGN_*
variables — running just node android won’t pick up the keystore (.jks).
Installing on a device
With your phone connected (or an emulator running), install the APK:
adb install -r <path-to-apk>
Troubleshooting notes
-
Don’t build in a shared folder — if the repo is on a VirtualBox shared folder (e.g.
/media/sf_shared/), you’ll run into permission issues and Git’s “dubious ownership” error. Clone the repo into your home directory (e.g.~/tutanota) instead of the shared folder:git clone https://github.com/tutao/tutanota.git ~/tutanota -
Disk space — the build uses a lot of space. After a full build, the whole
tutanotafolder came in at about 6.8 GB, so make sure you have enough free disk space before you start. -
NDK not found — install the exact version the build reports via
sdkmanager "ndk;<version>". See the NDK downloads page if you want to check versions without re-running the build. -
Signing key not found — make sure the
.jkskeystore is insideapp-android/app/, sinceAPK_SIGN_STOREis given as a relative filename. -
Capturing the log — the build produces a lot of output. Pipe it to a file so you can scroll back through it:
APK_SIGN_ALIAS="tutaKey" APK_SIGN_STORE='MyKeystore.jks' APK_SIGN_STORE_PASS="CHANGEME" APK_SIGN_KEY_PASS="CHANGEME" node android 2>&1 | tee -a build.log
Let me know what you think of this article on twitter @lukahn!