From 1fa2cf36078110839298d95b6a598f3e47d7c967 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 15:00:39 +0300 Subject: [PATCH 01/10] Respect X-Persist header to skip saving messages to local history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the server sends "persist": false in the message payload (set via the X-Persist: no publish header), the Android client skips inserting the notification into the local Room database. The notification is still delivered and shown as a popup, but will not appear in the app's message history. The persist field follows the same @Ignore pattern as the existing event field — it is an in-memory-only attribute, not stored in the database. Co-Authored-By: Claude Sonnet 4.6 --- app/src/main/java/io/heckel/ntfy/db/Database.kt | 1 + app/src/main/java/io/heckel/ntfy/db/Repository.kt | 12 +++++++----- app/src/main/java/io/heckel/ntfy/msg/Message.kt | 1 + .../java/io/heckel/ntfy/msg/NotificationParser.kt | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 3101ae2c1..48fa4799a 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -161,6 +161,7 @@ data class Notification( @Embedded(prefix = "attachment_") val attachment: Attachment?, @ColumnInfo(name = "deleted") val deleted: Boolean, @Ignore val event: String = ApiService.EVENT_MESSAGE, // In-memory event type (message, message_delete, message_clear) + @Ignore val persist: Boolean = true, // If false, notification should not be saved to local history ) { constructor( id: String, diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 4de802d82..b4fcd5b18 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -139,12 +139,14 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) if (maybeExistingNotification != null || notification.event != ApiService.EVENT_MESSAGE) { return false } - // Mark old notifications with the same sequence ID as deleted (this is an update to an existing sequence) - if (notification.sequenceId.isNotEmpty()) { - notificationDao.markAsDeletedBySequenceId(notification.subscriptionId, notification.sequenceId) - } subscriptionDao.updateLastNotificationId(notification.subscriptionId, notification.id) - notificationDao.add(notification) + if (notification.persist) { + // Mark old notifications with the same sequence ID as deleted (this is an update to an existing sequence) + if (notification.sequenceId.isNotEmpty()) { + notificationDao.markAsDeletedBySequenceId(notification.subscriptionId, notification.sequenceId) + } + notificationDao.add(notification) + } return true } diff --git a/app/src/main/java/io/heckel/ntfy/msg/Message.kt b/app/src/main/java/io/heckel/ntfy/msg/Message.kt index d20720a75..ec0f29e80 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/Message.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/Message.kt @@ -22,6 +22,7 @@ data class Message( @SerializedName("content_type") val contentType: String?, val encoding: String?, val attachment: MessageAttachment?, + val persist: Boolean?, ) @Keep diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt index 250232952..40f8b2ecc 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt @@ -73,7 +73,8 @@ class NotificationParser { attachment = attachment, notificationId = deriveNotificationId(baseUrl, topic, sequenceId), deleted = false, - event = message.event + event = message.event, + persist = message.persist ?: true, ) return NotificationWithTopic(topic, notification) } From d14de71aeea825399aca084fbf83d7f5b15bd809 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 15:19:25 +0300 Subject: [PATCH 02/10] Add GitHub Actions workflow to build F-Droid debug APK --- .github/workflows/build.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..dcf701a8b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build APK + +on: + push: + branches: [ "**" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - uses: gradle/actions/setup-gradle@v4 + + - name: Build F-Droid debug APK + run: ./gradlew assembleFdroidDebug + + - uses: actions/upload-artifact@v4 + with: + name: ntfy-fdroid-debug + path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk + retention-days: 7 From 11e7a16ea191591eed327d4f7ed3bb2f7004b200 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 15:27:12 +0300 Subject: [PATCH 03/10] Add scheduled workflow to rebase on upstream main daily --- .github/workflows/rebase.yml | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/rebase.yml diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 000000000..faa2606e6 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,44 @@ +name: Rebase on upstream main + +on: + schedule: + - cron: '0 6 * * *' # daily at 6am UTC + workflow_dispatch: # allow manual trigger + +jobs: + rebase: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: x-persist-header + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Add upstream remote + run: git remote add upstream https://github.com/binwiederhier/ntfy-android.git + + - name: Fetch upstream main + run: git fetch upstream main + + - name: Check for new upstream commits + id: check + run: | + UPSTREAM=$(git rev-parse upstream/main) + BASE=$(git merge-base HEAD upstream/main) + if [ "$UPSTREAM" = "$BASE" ]; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Rebase on upstream main + if: steps.check.outputs.changed == 'true' + run: | + git config user.email "action@github.com" + git config user.name "GitHub Actions" + git rebase upstream/main + + - name: Push rebased branch + if: steps.check.outputs.changed == 'true' + run: git push origin x-persist-header --force From 22e6707e8ecac7c5bf49bbddb975146a16c7c490 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 16:35:13 +0300 Subject: [PATCH 04/10] Build signed F-Droid release APK instead of debug Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dcf701a8b..6245254e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,11 +17,23 @@ jobs: - uses: gradle/actions/setup-gradle@v4 - - name: Build F-Droid debug APK - run: ./gradlew assembleFdroidDebug + - name: Build F-Droid release APK (unsigned) + run: ./gradlew assembleFdroidRelease + + - name: Sign APK + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/ntfy-release.keystore + $ANDROID_SDK_ROOT/build-tools/$(ls $ANDROID_SDK_ROOT/build-tools | sort -V | tail -1)/apksigner sign \ + --ks /tmp/ntfy-release.keystore \ + --ks-key-alias "${{ secrets.KEY_ALIAS }}" \ + --ks-pass "pass:${{ secrets.KEYSTORE_PASSWORD }}" \ + --key-pass "pass:${{ secrets.KEY_PASSWORD }}" \ + --out app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk \ + app/build/outputs/apk/fdroid/release/app-fdroid-release.apk + rm /tmp/ntfy-release.keystore - uses: actions/upload-artifact@v4 with: - name: ntfy-fdroid-debug - path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk + name: ntfy-fdroid-release + path: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk retention-days: 7 From d3daad971d9db3a6e5129f68125404309449ee37 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 16:48:32 +0300 Subject: [PATCH 05/10] Fix unsigned APK filename for release build Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6245254e5..ee97c8f47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: --ks-pass "pass:${{ secrets.KEYSTORE_PASSWORD }}" \ --key-pass "pass:${{ secrets.KEY_PASSWORD }}" \ --out app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk \ - app/build/outputs/apk/fdroid/release/app-fdroid-release.apk + app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk rm /tmp/ntfy-release.keystore - uses: actions/upload-artifact@v4 From 1bbacceeb5f0f56555ee0914c9daa5bc5d53c4dd Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 17:54:39 +0300 Subject: [PATCH 06/10] Send signed APK as email attachment after build --- .github/workflows/build.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee97c8f47..1240385ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,3 +37,19 @@ jobs: name: ntfy-fdroid-release path: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk retention-days: 7 + + - name: Send APK via email + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.NOTIFY_EMAIL }} + password: ${{ secrets.GMAIL_APP_PASSWORD }} + subject: "ntfy Android build — ${{ github.ref_name }}@${{ github.sha && github.sha || '' }}" + to: ${{ secrets.NOTIFY_EMAIL }} + from: ${{ secrets.NOTIFY_FROM }} + body: | + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + ${{ github.event.head_commit.message }} + attachments: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk From 2115a6a9e057cdf5f1563841959591163e209d81 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 18:08:01 +0300 Subject: [PATCH 07/10] Add workflow_dispatch trigger --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1240385ac..a2e859a77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,7 @@ name: Build APK on: push: branches: [ "**" ] + workflow_dispatch: jobs: build: From 3d9e3db92d117c58216fed0c9baf8369af8592c9 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 18:12:32 +0300 Subject: [PATCH 08/10] Zip APK before emailing (Gmail blocks .apk attachments) --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2e859a77..45e4b510c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,6 +32,8 @@ jobs: --out app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk \ app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk rm /tmp/ntfy-release.keystore + zip app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip \ + app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk - uses: actions/upload-artifact@v4 with: @@ -53,4 +55,4 @@ jobs: Branch: ${{ github.ref_name }} Commit: ${{ github.sha }} ${{ github.event.head_commit.message }} - attachments: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk + attachments: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip From 7e7c5eaa235edcb85e7cadfe68700d8adf37e83e Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 18:31:29 +0300 Subject: [PATCH 09/10] Single success/failure email with consistent subject format --- .github/workflows/build.yml | 44 ++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45e4b510c..89d31407a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,10 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Get short SHA + id: sha + run: echo "short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + - uses: actions/setup-java@v4 with: java-version: '17' @@ -32,7 +36,7 @@ jobs: --out app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk \ app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk rm /tmp/ntfy-release.keystore - zip app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip \ + zip -P ntfy app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip \ app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk - uses: actions/upload-artifact@v4 @@ -41,18 +45,46 @@ jobs: path: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk retention-days: 7 - - name: Send APK via email + - name: Send success email + if: success() + continue-on-error: true uses: dawidd6/action-send-mail@v3 with: server_address: smtp.gmail.com server_port: 587 username: ${{ secrets.NOTIFY_EMAIL }} password: ${{ secrets.GMAIL_APP_PASSWORD }} - subject: "ntfy Android build — ${{ github.ref_name }}@${{ github.sha && github.sha || '' }}" + subject: "[EugeneShtoka/ntfy-android] Build succeeded: ${{ github.ref_name }} (${{ steps.sha.outputs.short }})" to: ${{ secrets.NOTIFY_EMAIL }} from: ${{ secrets.NOTIFY_FROM }} body: | - Branch: ${{ github.ref_name }} - Commit: ${{ github.sha }} - ${{ github.event.head_commit.message }} + ✅ Build succeeded + + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Message: ${{ github.event.head_commit.message }} + + APK is attached (zip password: ntfy). attachments: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip + + - name: Send failure email + if: failure() + continue-on-error: true + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ secrets.NOTIFY_EMAIL }} + password: ${{ secrets.GMAIL_APP_PASSWORD }} + subject: "[EugeneShtoka/ntfy-android] Build failed: ${{ github.ref_name }} (${{ steps.sha.outputs.short }})" + to: ${{ secrets.NOTIFY_EMAIL }} + from: ${{ secrets.NOTIFY_FROM }} + body: | + ❌ Build failed + + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Message: ${{ github.event.head_commit.message }} + + Check the run for details: + https://github.com/EugeneShtoka/ntfy-android/actions/runs/${{ github.run_id }} From 6bc67955d0b9605e57225c1a3faa5ffef1e2e294 Mon Sep 17 00:00:00 2001 From: Eugene Shtoka Date: Mon, 4 May 2026 19:07:03 +0300 Subject: [PATCH 10/10] Email link to artifacts instead of attachment --- .github/workflows/build.yml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 89d31407a..49c5f49c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,8 +36,6 @@ jobs: --out app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk \ app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk rm /tmp/ntfy-release.keystore - zip -P ntfy app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip \ - app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.apk - uses: actions/upload-artifact@v4 with: @@ -58,33 +56,9 @@ jobs: to: ${{ secrets.NOTIFY_EMAIL }} from: ${{ secrets.NOTIFY_FROM }} body: | - ✅ Build succeeded - - Branch: ${{ github.ref_name }} - Commit: ${{ github.sha }} - Message: ${{ github.event.head_commit.message }} - - APK is attached (zip password: ntfy). - attachments: app/build/outputs/apk/fdroid/release/app-fdroid-release-signed.zip - - - name: Send failure email - if: failure() - continue-on-error: true - uses: dawidd6/action-send-mail@v3 - with: - server_address: smtp.gmail.com - server_port: 587 - username: ${{ secrets.NOTIFY_EMAIL }} - password: ${{ secrets.GMAIL_APP_PASSWORD }} - subject: "[EugeneShtoka/ntfy-android] Build failed: ${{ github.ref_name }} (${{ steps.sha.outputs.short }})" - to: ${{ secrets.NOTIFY_EMAIL }} - from: ${{ secrets.NOTIFY_FROM }} - body: | - ❌ Build failed - Branch: ${{ github.ref_name }} Commit: ${{ github.sha }} Message: ${{ github.event.head_commit.message }} - Check the run for details: + Download APK: https://github.com/EugeneShtoka/ntfy-android/actions/runs/${{ github.run_id }}