About the project

🫙 A notification you answer with a pencil. Circle a word today. Days later the page returns with your circle on it: "3 days ago you circled 'stuck'. Today?"

  • The notification image is your own mark, drawn on the phone as it arrives.
  • It knows when to stop. "Not today" is respected twice, two a day max, and every Journey has an exit.
  • OneSignal can see silence. Every push the phone holds back is reported with its reason.
  • Smart, and discreet. OneSignal targets a few simple states (a page is due, you're resting), so it knows exactly when to knock while your drawings stay on your phone.

💡 Inspiration

I'm a software engineer, and this year the tech job market has been brutal. My mornings started with Slack, job news and a phone that never stopped buzzing. I also opened an affirmation app every day. It came in bursts or not at all, said the same things, and never remembered I'd been there yesterday. The calm thing had become part of the noise.

So I built the opposite: one small page a day, answered with a pencil in a second, that comes back days later with my own scribble on it, like a note from a past me who made it through. And when I say "not today", it actually listens.

Drawing it was the fun part. Knowing when to knock, how often, and when to stay quiet is a messaging problem, and that's OneSignal's job. Without it, Lightjar is a drawing app nobody comes back to.

🫙 What it does

  • One page a day at 8:00 (10:30 on slow days), from a small creature: a kind line in its wobbly handwriting, a dry aside "(I tried. the jar was fine.)", and three words to circle.
  • Answer from the notification. Tap Keep, or a paper sheet slides over your screen. Circle, lift, gone.
  • The page travels and comes back with your mark, faded and dated. Five visits, then it stands on your shelf.
  • It changes with your day. Cream mornings, rose-grey rain, a dim blue night with the jar glowing, and the creature in sunglasses on your day off.
  • Not today is on every notification. "(nobody told me. I rested anyway.)"

🛠️ How we built it

The first push carried 13 bytes, {"lj":"auto"}, and the phone drew the creature in sunglasses: a day off. That became the architecture. OneSignal decides when and to whom. The phone decides what.

A respectful notification Lightjar OneSignal
comes at the right time 8:00 local, 10:30 on slow days; returns when you usually pick up your phone Timezone delivery · Slow tomorrow segment · Intelligent Delivery
knows when to stop two a day max, one page in the shade Collapse ID · cap reported as events
hears "not today" one tap and it rests Resting segment · Not today Journey
starts gently the first week First week Journey · in-app message
remembers you the page returns with your mark Page due segment built on a tag
  • Tags are small and precise. 8 tags, each a state a segment needs (next_return_at, resting_until…). History goes to custom events.
  • Journeys have exits. Not today waits a day, re-checks Resting, sends one gentle return, and lets you go the moment you answer.
  • Sends can't double up. The REST API is called with idempotency keys. The day's line rides in the push data, so new words reach every phone without an app update.
  • Answers and silence are both events. mark_added {from_notification} counts answers that never open the app. page_held_back {reason} counts the pushes the phone chose to keep quiet.

On the phone, a notification extension decides at arrival (today's page, a return, or nothing if you're resting) and draws it with the app's own Compose code, running headless.

With RevenueCat, the paywall appears once, when a fourth page has to wait, with your own page as its picture: "(the free jar holds three pages travelling at once. that's plenty, honestly.)" Three plans come from one offering behind one plus entitlement, and setOnesignalUserID makes each customer the same OneSignal user.

🧗 Challenges we ran into

  • My Resting bug. Segments are evaluated when a send is created, the day before, so resting yesterday cost you today's page. Now the phone also checks on arrival. The server can't know how you feel this morning. The phone can.
  • A cleaner data model. The tag limit pushed me to keep states in tags and history in events, and both got better for it.
  • Two a day, from every sender. The cap lives on the phone, so it covers Journeys and API sends alike, and OneSignal hears why a push was held back.

🏆 Accomplishments that we're proud of

  • 28 Sept, 8:44: a returning page got a circle around "slow" straight from the notification. The app never opened.
  • Brand new on Google Play. 11 phones get the daily page. 15 people entered First week and 7 completed it. Clicks stay near zero by design, because answers don't need the app.

📚 What we learned

  • Opens undercount the best notifications. Count answers.
  • "Not today" must be respected twice: when targeting, and when the push arrives.
  • Limits are free design reviews.

🚀 What's next for Lightjar

Trial reminder (RevenueCat → OneSignal) · stop after two unanswered returns · weekly tuning through the OneSignal MCP · iOS.

"(the jar only does little. it's plenty.)"

Built With

  • android
  • compose
  • github-actions
  • jetpack
  • kotlin
  • kotlin-multiplatform
  • onesignal
  • revenuecat
Share this project:

Updates

Submission history