hihilabs / expo
ANDROID MODULE
// sms_autoreplier v1.0

Incoming SMS.
Instant reply.

A native Android background service that watches for keyword patterns in incoming texts and fires a configurable auto-reply in milliseconds. Built for the on-call hustle.

● ANDROID ONLY
EXPO SDK 52
KOTLIN NATIVE
ZERO BACKEND
FREE
SMS arrives
BroadcastReceiver fires
keyword scan
regex extract
auto-reply sent

Runs entirely on-device. No server, no webhook, no latency. The Kotlin BroadcastReceiver fires before the screen even lights up.

INCOMING SMS
Open shift available at River Haven (May 10, 7am–3pm).
Reply 1795558 to claim this shift.
AUTO-REPLY →
1795558
extracted & sent in <200ms
pattern: reply\s+([\w\d]+)  ·  capture group 1 → sent as reply
CCC Shift Claim
"Open shift available"
extract token
mode: regex extract  ·  pattern: reply\s+([\w\d]+)
Example: Static Reply
"Are you available"
"yes, on my way"
mode: static reply

Rules stored in filesDir/sms_rules.json — read directly by the native receiver with no IPC overhead.

BUILD FROM SOURCE
Expo + Android Studio
Download the project source, drop in SpaceMono font, run npm install && npx expo prebuild --platform android and sideload your own APK.
download source →
PRE-BUILT APK
Request a Build
Pre-built APK coming soon. Drop an email and we'll send you a sideload-ready debug build for Android 8+.
request apk →
// fires on every incoming SMS, reads rules from filesDir
class SmsReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent)
        val rulesFile = File(context.filesDir, "sms_rules.json")
        val rules = JSONArray(if (rulesFile.exists()) rulesFile.readText() else "[]")

        for (msg in messages) {
            for (i in 0 until rules.length()) {
                val rule = rules.getJSONObject(i)
                if (!rule.optBoolean("enabled", true)) continue
                if (!msg.messageBody.contains(rule.optString("keyword"), ignoreCase = true)) continue

                val reply = Regex(rule.optString("extractPattern"), RegexOption.IGNORE_CASE)
                    .find(msg.messageBody)?.groupValues?.getOrNull(1) ?: continue

                smsManager.sendTextMessage(msg.originatingAddress, null, reply, null, null)
                writeLog(context, msg.originatingAddress, rule.optString("keyword"), reply)
                break
            }
        }
    }
}