SendRateLimiter answers "may I send now?". When the answer was no, the
bulk worker called `break` -- it abandoned the rest of the list and told
the user "Stopped 20 short of 50, try the rest later". That protects the
carrier ceiling but is useless as a way to deliver 200 messages: the
user has to babysit the app and re-run it seven times.
SendPacing is the other half: a fixed gap between sends, so a batch
stays under the ceiling by construction and runs to completion.
* MAX_DELAY_MS caps at 10 minutes -- past that a batch of any size
takes days and this app is not a scheduler.
* RECOMMENDED_DELAY_MS is derived, not hardcoded: window / capacity,
i.e. one per minute for the default 30-per-30-minutes.
* Default is the recommended gap, NOT zero. A user who never touches
the setting should get a batch that completes rather than one that
dies a third of the way through.
* from_millis clamps instead of rejecting: it is fed by a text field,
and refusing to send because someone typed 9999 is worse than
quietly using the maximum. from_seconds uses saturating_mul so
i64::MAX seconds cannot wrap to a negative delay.
* total_duration_ms counts n-1 gaps, not n. Nothing waits before the
first message or after the last, and the off-by-one is visible to
the user on small batches.
Pure arithmetic with no sleeping, so the schedule is asserted in unit
tests rather than observed. 10 new tests, the important pair being:
the_recommended_gap_keeps_a_long_batch_under_the_limiter
walks 200 sends through a real SendRateLimiter at the paced
timestamps and asserts none is refused
without_pacing_the_same_batch_is_refused_at_the_cap
the same 200 sends with no gap stop at exactly 30
robius-sms: 37 -> 47 lib tests. Negative-tested by changing the default
back to zero, which fails 2 tests.
|
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| README.md | ||
robius-sms
A Rust library to access and send SMS messages across multiple platforms.
Currently, advanced SMS functionality is implemented on Android only.
Android capabilities
This crate currently supports:
- sending a single SMS
- sending bulk SMS to multiple recipients
- listing SMS messages from the device SMS database
- checking/requesting SMS permissions
- scheduling recurring SMS sends on Android
Android manifest permissions
Add these to your AndroidManifest.xml:
<manifest ... >
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Only needed if you call schedule_sms(). Android 12+ refuses exact
alarms without it, and it is special-access: the user must enable
"Alarms & reminders" in Settings, a runtime prompt cannot grant
it. Without it, scheduled sends are batched by Doze. -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application ...>
<receiver
android:name="robius.sms.SmsAlarmReceiver"
android:exported="false" />
<receiver
android:name="robius.sms.SmsBootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
Standard iOS (stubs)
cargo build --target aarch64-apple-ios
TrollStore iOS (silent SMS + message reading)
cargo build --target aarch64-apple-ios --features trollstore
Delivery status
send_sms returning Ok means the send was handed to the platform, not
that it was delivered. Do not show that to a user as "delivered".
Where the UI makes a claim about a message, use send_sms_tracked:
robius_sms::set_send_result_waker(wake_my_event_loop);
robius_sms::register_send_receiver()?;
let token = robius_sms::send_sms_tracked(&request)?;
// later, on the UI thread:
for report in robius_sms::take_send_reports() {
if report.token == token {
match report.outcome {
robius_sms::SendOutcome::Sent => { /* radio accepted it */ }
robius_sms::SendOutcome::Delivered => { /* handset ack'd */ }
robius_sms::SendOutcome::SendFailed { code }
| robius_sms::SendOutcome::DeliveryFailed { code } => { /* show it */ }
}
}
}
Scheduled messages are encrypted at rest
Pending schedules must outlive the process so SmsAlarmReceiver can send
them when the alarm fires, so recipient and body go to SharedPreferences.
They are AES-256-GCM enveloped with a non-exportable key held in the
platform AndroidKeyStore. If the keystore is unavailable, schedule_sms
fails rather than storing plaintext.
Cost
You are billed per segment. Call segment_count(body) before sending:
any character outside the GSM-7 alphabet -- one emoji is enough --
forces the whole message to UCS-2, dropping the per-part limit from 160
characters to 70. Bodies longer than one part are sent with
sendMultipartTextMessage.
Note that the accented Latin characters common in Swahili, French and German (e, a, o, n, u with diacritics) ARE in GSM-7 and do not trigger this.