Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Privacy Policy

Last updated: October 15, 2026.
Last updated: October 19, 2026.

Message487 forwards selected notifications and new incoming SMS to a webhook you configure.
Its core SMS feature transfers new incoming SMS from Android to your n8n workflow so you can
view them on a computer.
Notification events can include titles, text, image attachments and optional avatars or app icons.
SMS events include text, sender and time. Your n8n workflow or another HTTPS receiver decides
how to process or forward these events; the app does not depend on Telegram.
Both capture sources are off by default. Notifications require Android notification access and
selection of source apps. Optional automatic selection of newly detected launcher apps is off by default;
when enabled, it runs when you open or return to Message487 and preserves manual exclusions.
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
</p>

Message487 connects selected Android notifications and incoming SMS to your n8n workflows.
A custom webhook is also supported. Telegram forwarding is one possible workflow; the Android
app does not depend on Telegram.
Forward notification text and images, with optional sender avatars and app icons, or enable SMS
forwarding separately. A custom HTTPS webhook is also supported. Telegram forwarding is one
possible workflow; the Android app does not depend on Telegram.

**Status:** development preview with notification/SMS capture, a persistent private outbox,
background delivery, automatic retries and a delivery journal. Webhook requests require a Bearer token, stored encrypted on the device. Signed APK release automation is configured; see [Releases](docs/en/releases.md) and [release automation](docs/en/release-automation.md), and [release testing](docs/en/release-testing.md).
Expand Down Expand Up @@ -193,7 +194,9 @@ PR CI has no release signing credentials. It runs JVM/lint checks and instrument
on disposable API 26 and API 35 emulators. JUnit results appear in GitHub Checks; debug and
unsigned release APK links are kept in the PR description. See [testing](docs/en/testing.md).
For signed APK releases, see [Releases](docs/en/releases.md) and [release automation](docs/en/release-automation.md), and [release testing](docs/en/release-testing.md).
Store graphics and their provenance are documented in [Branding](assets/branding/README.md).
English and Russian store descriptions for F-Droid and Google Play share the canonical
[Fastlane metadata](fastlane/metadata/android). Store graphics and their provenance are documented
in [Branding](assets/branding/README.md).

See the [project context](docs/en/project-context.md) for remaining product decisions.
This project succeeds [sms487](https://github.com/andre487/sms487).
Expand Down
16 changes: 6 additions & 10 deletions app/src/main/res/drawable/ic_launcher_background.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportHeight="108">
<vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
<path android:pathData="M0,0H108V108H0Z">
<aapt:attr name="android:fillColor">
<gradient android:type="linear" android:startX="0" android:startY="0"
android:endX="108" android:endY="108">
<item android:offset="0" android:color="#00C9EF" />
<item android:offset="0.35" android:color="#0754F5" />
<item android:offset="0.65" android:color="#6500ED" />
<item android:offset="1" android:color="#FF1AB9" />
<gradient android:type="linear" android:startX="0" android:startY="0" android:endX="108" android:endY="108">
<item android:offset="0" android:color="#FF00C9EF" />
<item android:offset=".35" android:color="#FF0754F5" />
<item android:offset=".65" android:color="#FF6500ED" />
<item android:offset="1" android:color="#FFFF1AB9" />
</gradient>
</aapt:attr>
</path>
Expand Down
20 changes: 6 additions & 14 deletions app/src/main/res/drawable/ic_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:height="108dp"
android:viewportWidth="1254" android:viewportHeight="1254">
<group android:scaleX="0.65" android:scaleY="0.65"
android:translateX="219.45" android:translateY="219.45">
<path android:fillColor="#FFFFFFFF" android:pathData="M313,298H877Q942,298 942,363V563H878V377Q878,361 862,361H329Q313,361 313,377V711Q313,727 329,727H764V790H313Q248,790 248,725V363Q248,298 313,298Z M544,790H642V857H698Q730,857 730,887Q730,917 698,917H488Q456,917 456,887Q456,857 488,857H544Z" />
<path android:fillColor="#FFFFFFFF" android:fillType="evenOdd"
android:pathData="M819,563H960Q1015,563 1015,618V887Q1015,942 960,942H819Q764,942 764,887V618Q764,563 819,563Z M831,616Q813,616 813,634V870Q813,888 831,888H948Q966,888 966,870V634Q966,616 948,616Z" />
<path android:fillColor="@android:color/transparent" android:strokeColor="#FFFFFFFF"
android:strokeWidth="17" android:strokeLineJoin="round" android:strokeLineCap="round"
android:pathData="M508,522L590,465L672,522V611H508Z M508,522L546,547H634L672,522" />
<path android:fillColor="@android:color/transparent" android:strokeColor="#FFFFFFFF"
android:strokeWidth="11" android:strokeLineJoin="round" android:strokeLineCap="round"
android:pathData="M855,725H923Q929,725 929,731V783Q929,789 923,789H855Q849,789 849,783V731Q849,725 855,725Z M850,731L889,761L928,731" />
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="1254" android:viewportHeight="1254">
<group android:translateX="169.29" android:translateY="169.29" android:scaleX="0.73" android:scaleY="0.73">
<path android:fillColor="#FFffffff" android:pathData="M313,298H877Q942,298 942,363V563H878V377Q878,361 862,361H329Q313,361 313,377V711Q313,727 329,727H764V790H313Q248,790 248,725V363Q248,298 313,298Z M544,790H642V857H698Q730,857 730,887Q730,917 698,917H488Q456,917 456,887Q456,857 488,857H544Z" />
<path android:fillColor="#FFffffff" android:pathData="M819,563H960Q1015,563 1015,618V887Q1015,942 960,942H819Q764,942 764,887V618Q764,563 819,563Z M831,616Q813,616 813,634V870Q813,888 831,888H948Q966,888 966,870V634Q966,616 948,616Z" android:fillType="evenOdd" />
<path android:fillColor="#00000000" android:pathData="M508,522L590,465L672,522V611H508Z M508,522L546,547H634L672,522" android:strokeColor="#FFffffff" android:strokeWidth="17" android:strokeLineJoin="round" android:strokeLineCap="round" />
<path android:fillColor="#00000000" android:pathData="M855,725H923Q929,725 929,731V783Q929,789 923,789H855Q849,789 849,783V731Q849,725 855,725Z M850,731L889,761L928,731" android:strokeColor="#FFffffff" android:strokeWidth="11" android:strokeLineJoin="round" android:strokeLineCap="round" />
</group>
</vector>
7 changes: 2 additions & 5 deletions app/src/main/res/drawable/ic_notification.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:fillType="evenOdd"
android:pathData="M3,3H19Q21,3 21,5V8H19V5H3V16H14V18H12V20H15V22H6V20H9V18H3Q1,18 1,16V5Q1,3 3,3Z M17,9H21Q23,9 23,11V20Q23,22 21,22H17Q15,22 15,20V11Q15,9 17,9Z M17,11V20H21V11Z" />
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:fillType="evenOdd" android:pathData="M3 3H19Q21 3 21 5V8H19V5H3V16H14V18H12V20H15V22H6V20H9V18H3Q1 18 1 16V5Q1 3 3 3Z M17 9H21Q23 9 23 11V20Q23 22 21 22H17Q15 22 15 20V11Q15 9 17 9Z M17 11V20H21V11Z" />
</vector>
63 changes: 51 additions & 12 deletions assets/branding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,69 @@ Sizes: square/round icon 512×512, status icon 24×24, feature graphic 1024×500
The round and status PNGs retain transparency; the white status mark needs a contrasting background.

Android adaptive layers live in `app/src/main/res/drawable/ic_launcher_{background,foreground}.xml`.
The adaptive foreground and round SVG use scale `0.65` about the center of their 1254-unit
viewport for extra circular-mask padding. The square SVG retains scale `0.73`.
The adaptive foreground follows the square SVG at scale `0.73` about the center of its
1254-unit viewport. The separate round SVG retains scale `0.65` for extra circular-mask padding.
Both SVGs share the Android paths and gradient, cropped to the central 836×836 viewport.
Keep these sources synchronized when changing the artwork.
The SVG files remain separate editable sources; regenerate derived files after editing them.
The themed launcher and notification small icon share `ic_notification.xml`, the simplified
white monitor/phone silhouette without envelopes or gradient. Update notifications use this
small icon. The app UI retains its purple Material theme and standard Material icons.

Export PNGs with the installed `rsvg-convert` utility:
Generate all derived assets from the **separate** SVG sources:

```sh
for name in icon icon-round status-icon feature; do
rsvg-convert "assets/branding/$name.svg" -o "assets/branding/$name.png"
done
rsvg-convert assets/branding/icon.svg -o fastlane/metadata/android/en-US/images/icon.png
rsvg-convert assets/branding/feature.svg -o /tmp/message487-feature.png
magick /tmp/message487-feature.png -background white -alpha remove -alpha off -type TrueColor fastlane/metadata/android/en-US/images/featureGraphic.png
python3 scripts/generate-branding.py
```

For launcher PNGs use `rsvg-convert -w SIZE -h SIZE` with `icon.svg` or `icon-round.svg`.
Sizes by density: mdpi 48, hdpi 72, xhdpi 96, xxhdpi 144, xxxhdpi 192.
Requires `rsvg-convert` and ImageMagick's `magick`. Run from any directory.
Use `--output-root /tmp/message487-branding` to inspect exports without overwriting repository files.

| Source | Generated assets |
| --- | --- |
| `icon.svg` | Square branding PNG, opaque RGB store icon, square launcher PNGs, adaptive foreground/background XML |
| `icon-round.svg` | Transparent round branding PNG, round launcher PNGs |
| `status-icon.svg` | Transparent 24×24 PNG and `ic_notification.xml` |
| `feature.svg` | Branding banner PNG and opaque RGB store feature graphic |

Launcher PNG sizes: mdpi 48, hdpi 72, xhdpi 96, xxhdpi 144, xxxhdpi 192.
The script preserves each source's layout, including the square/round scale difference.
The Android vector exporter supports the existing path fill/stroke attributes, the square
artwork's `translate(x y) scale(s)` transform, and its diagonal background gradient.
Unsupported path features or gradient changes cause an error instead of silently losing details.
Adaptive-icon and monochrome-inset XML files only reference these generated drawables and
remain static. Samsung previews are optional and use the separate command below.

SVGs are self-contained: no external images, fonts, scripts or raster embeddings.
The feature graphic is promotional artwork, not an app screenshot.

Preview the adaptive artwork with an approximate Samsung-style rounded-square mask:

```sh
python3 scripts/preview-samsung-icon.py --source assets/branding/icon.svg /tmp/message487-samsung.png
# Edit the source SVG, then rerun the same command to regenerate the PNG.
# Use an .svg output path instead to generate a masked vector preview.
```

Output defaults to 140×140 pixels, approximately the icon size in the supplied screenshot.
Use `--size 512` for a larger preview.
SVG output uses only Python's standard library; PNG output also needs `rsvg-convert`.
Use `--source` to choose another SVG; its `viewBox` defines the mask bounds.
By default the preview reuses `icon.svg`, which shares the adaptive foreground's scale and
position. Keep it synchronized with the Android layers as described above. The mask
approximates the supplied Samsung screenshot. Shadow radii (1/96 and 1/48 of icon size),
alpha (41/255 and 26/255), and 0.96 artwork scale follow the published
[SemAppIconSolution implementation](https://github.com/488315/samsung_framework/blob/master/framework/sources/android/app/SemAppIconSolution.java).
SVG Gaussian blur is not identical to Android's Skia shadow rendering. The inner highlight
and shade are a visual approximation, not a recovered Samsung bevel algorithm.
Use `--bevel 0.3` to adjust edge strength (0–1), or `--flat` for the mask alone.
Themes, night-mode tint and animations are not reproduced.

For a device-rendered reference, [One UI Icon Extractor](https://github.com/Palmoe/OneUI-Icon-Extractor)
uses Samsung's `semGetApplicationIconForIconTray` / `semGetActivityIconForIconTray` APIs
on a Samsung device, retaining One UI effects. Compare its export from the target phone
with this preview before claiming an exact match. This is a preview, not another Android
launcher resource.

Earlier artwork originated from
[andre487/sms487](https://github.com/andre487/sms487/tree/d4aca0724c4d8c8cfcfe128c6df6cc93f64625f2/client/app/src/main)
under the [sms487 MIT license](sms487-LICENSE); the original license is retained.
Expand Down
34 changes: 21 additions & 13 deletions docs/en/project-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

[English](../en/project-context.md) | [Русский](../ru/project-context.md)

Updated September 8, 2026.
Updated October 19, 2026.

## Confirmed by the user

- A new Android application replaces sms487 in a separate repository.
- Package ID: `life.andre.message487`.
- Primary positioning is n8n integration; arbitrary webhooks must also be supported.
- Primary positioning is forwarding selected Android notifications, images and new incoming SMS
to n8n workflows; arbitrary HTTPS webhooks are also supported.
- Project presentation, code style, privacy policy, documentation and CI follow
[AndroidMegaProxy](https://github.com/andre487/AndroidMegaProxy).

Expand Down Expand Up @@ -45,13 +46,21 @@ Compatibility with that protocol was not agreed as a requirement for the new app
## Capture and delivery

NotificationListenerService uses a package selection; SMS_RECEIVED uses RECEIVE_SMS and
goAsync. Both sources default to off. Existing SMS history is not read. Duplicate SMS and notifications
are suppressed by source package, exact original timestamp and text. The Sources switch is on
by default and allows opting out. Group summaries, ongoing notifications
and Message487's own notifications are excluded.

Events are persisted in SQLite before delivery; request bodies use AES-GCM with Android
Keystore. WorkManager retries transient failures; a periodic recovery task restores scheduling.
goAsync. Both sources default to off, with no apps selected. Existing SMS history and existing
notifications are not imported. Duplicate filters for SMS and notifications are independently
on by default. Matching uses the source package, exact text, image checksum and original
timestamps within a configurable inclusive time window (1 second by default). Titles and SMS
senders are excluded from the key. Group summaries, ongoing notifications and Message487's
own notifications are excluded.

Notifications can include image attachments and optional avatars/app icons. JPEG/PNG, quality
and output limits are configurable. See [README](../../README.md#delivery-and-data) for the
payload contract, image handling and duplicate-filter details.

Events are persisted before delivery. SQLite holds metadata and file references; request text
and credentials are AES-GCM encrypted in private files with Android Keystore. Images are
separate private files without additional app-level encryption.
WorkManager retries transient failures; a periodic recovery task restores scheduling.
Event ID, contents, URL and acknowledgement mode are captured together. Changing the connection
does not redirect existing queued events. Invalid ACKs and permanent HTTP errors require manual
retry. Unconfirmed events are not deleted by age; confirmed payloads are removed while a bounded
Expand All @@ -61,8 +70,7 @@ attempts; a running request may complete. Backup and device transfer exclude app
Limitations: capture depends on Android, and the process may die before local persistence.
WorkManager does not promise immediate delivery. Sensitive-notification restrictions are not
bypassed. Physical erasure of SQLite pages is not guaranteed. Deduplication hashes persist until app data is cleared, independently of journal cleanup.
The history starts with new captures after upgrading to database version 2. This does not replace
server-side deduplication by event ID.
This does not replace server-side deduplication by event ID.

## Product direction

Expand All @@ -79,8 +87,8 @@ viewing journal message contents are not implemented yet.
journal with contents hidden by default and manual retry.
- Webhook acceptance is distinct from delivery to Telegram or another downstream service.
Unconfirmed events are not silently removed by age.
- Diagnostics exclude secrets and message contents. Encryption, backup and retention requirements
must be decided before implementing storage.
- Diagnostics exclude secrets and message contents. Current encryption, backup and retention
behavior is documented in the [privacy policy](../../PRIVACY.md).

n8n has test and production URLs; persistent integrations use a published workflow's URL.
`Immediately` acknowledges workflow startup, not completion of its actions. See the
Expand Down
Loading
Loading