SMSManager Tutorial: Step-by-Step Guide for Developers

SMSManager Best Practices: Permissions, Delivery, and Security—

Android’s SmsManager is a core API for sending SMS messages programmatically. While simple in concept, using SmsManager safely and reliably requires attention to platform permissions, message delivery tracking, privacy, and security. This article covers practical best practices, code examples, and pitfalls to avoid when building SMS functionality into your Android app.


Overview: What SmsManager does and when to use it

SmsManager allows apps to send SMS messages from the device without invoking the system’s SMS app UI. Typical use cases:

  • Two-factor authentication (OTP) apps that send verification codes from the device.
  • Automation apps that send reports, reminders, or alerts.
  • Enterprise apps that integrate device-originated messaging into workflows.

Consider alternatives before using SmsManager:

  • Use Firebase Cloud Messaging (FCM) or server-side SMS providers (Twilio, Nexmo) when messages should come from a central number, require higher deliverability, or must be tracked centrally.
  • Use implicit intents (ACTION_SENDTO with sms:) to open the user’s SMS app when user interaction is appropriate or required by policy.

Permissions: requesting and handling

Runtime and manifest permissions

  • Declare permissions in AndroidManifest.xml:
    
    <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.READ_SMS"/> 
  • For Android 6.0+ request runtime permission for SEND_SMS (and RECEIVE/READ if used). Always check Permission APIs before attempting to send.

Best practices for requesting permissions

  • Request only the minimum permissions required. For sending, only request SEND_SMS.
  • Explain why the permission is needed with a short, contextual rationale (e.g., in-app dialog) before the system permission prompt.
  • Follow the principle of least privilege: avoid READ_SMS or RECEIVE_SMS unless your feature strictly needs them.
  • Handle “Don’t ask again”: detect when the user permanently denied permission and surface a guided flow to settings with a clear explanation.

Example runtime request (Kotlin)

private val SEND_SMS_REQ = 101 fun ensureSmsPermission(activity: Activity) {     if (ContextCompat.checkSelfPermission(activity, Manifest.permission.SEND_SMS)         != PackageManager.PERMISSION_GRANTED) {         if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.SEND_SMS)) {             // show rationale then request         } else {             ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.SEND_SMS), SEND_SMS_REQ)         }     } else {         // permission already granted     } } 

Respect user expectations and platform policies

  • Always be transparent: inform users when the app will send SMS and from which number.
  • Avoid sending SMS silently in the background without explicit user consent—this can be abusive and may violate Play Store policies.
  • For bulk or automated messaging consider using a server-based SMS provider instead of device-originated SMS to reduce spam risk and comply with carrier policies.

Sending messages: single and multipart

SmsManager provides methods for sending text and multipart text messages. Use sendTextMessage for short messages and sendMultipartTextMessage for longer messages that must be split and reassembled.

Kotlin example:

val smsManager = SmsManager.getDefault() val destination = "+1234567890" val text = "Hello from my app." smsManager.sendTextMessage(destination, null, text, sentPendingIntent, deliveredPendingIntent) 

For messages longer than the SMS length (typically 160 GSM-7 chars), use:

val parts = smsManager.divideMessage(longText) smsManager.sendMultipartTextMessage(destination, null, parts, sentIntents, deliveredIntents) 

Delivery reports and sent-status handling

  • To track whether SMS was sent and delivered, provide PendingIntent callbacks for the sent and delivery events.
  • Register BroadcastReceivers to receive results for these intents and handle different result codes: RESULT_OK, RESULT_ERROR_GENERIC_FAILURE, RESULT_ERROR_NO_SERVICE, RESULT_ERROR_NULL_PDU, RESULT_ERROR_RADIO_OFF.

Example (Kotlin):

val SENT = "SMS_SENT" val DELIVERED = "SMS_DELIVERED" val sentPI = PendingIntent.getBroadcast(context, 0, Intent(SENT), 0) val deliveredPI = PendingIntent.getBroadcast(context, 0, Intent(DELIVERED), 0) context.registerReceiver(object: BroadcastReceiver() {     override fun onReceive(ctx: Context, intent: Intent) {         when (resultCode) {             Activity.RESULT_OK -> Log.d("SMS", "Sent")             SmsManager.RESULT_ERROR_GENERIC_FAILURE -> Log.d("SMS", "Generic failure")             SmsManager.RESULT_ERROR_NO_SERVICE -> Log.d("SMS", "No service")             SmsManager.RESULT_ERROR_NULL_PDU -> Log.d("SMS", "Null PDU")             SmsManager.RESULT_ERROR_RADIO_OFF -> Log.d("SMS", "Radio off")         }     } }, IntentFilter(SENT)) 

Notes:

  • Delivery receipt behavior varies by carrier and destination number—delivery intent may not always arrive.
  • Use unique request codes or action strings if multiple messages are being tracked concurrently.

Security and privacy considerations

  • Never log or transmit SMS content to remote servers unless the user explicitly consents and you have a valid reason (e.g., backup). SMS often contains sensitive information (codes, personal messages).
  • If you must store SMS content locally, encrypt it at rest using platform keystore-backed keys.
  • Limit access to stored SMS data in your app via proper local access controls.
  • For OTP flows, prefer SMS Retriever API (Google Play services) or SMS User Consent API so the app can read one-time codes without requesting READ_SMS permission. These APIs improve privacy and UX.

SMS Retriever example (high level):

  • Server generates a verification code and includes an app hash in the SMS.
  • App listens for the SMS via Google Play services and retrieves the code without SMS read permission.

Rate limiting, retries, and backoff

  • Avoid sending large numbers of SMS in rapid succession—this can lead to carrier throttling or blocking.
  • Implement exponential backoff for retries when send fails due to transient errors (no service, radio off).
  • Show clear UI feedback to users for send failures and allow manual retry rather than silent repeated attempts.

Handling different Android versions and OEM behaviors

  • SmsManager APIs have been stable, but behavior around permissions, battery optimizations, and background execution changed across Android versions.
  • On Android 8+ background execution limits may affect background services that trigger SMS sends—use foreground services or scheduled tasks with user-visible notifications when appropriate.
  • OEMs may restrict SMS behavior; test on devices representative of your user base (Samsung, Xiaomi, Huawei) for quirks.

Testing and QA

  • Use real devices and test SIMs from multiple carriers to validate delivery and delivery receipts.
  • Test edge cases: no network, airplane mode, low signal, multipart messages, international numbers, and different character encodings (GSM vs. UCS-2).
  • Automate tests where possible but include manual tests for delivery receipt behavior.

Logging and telemetry (respect privacy)

  • Log only metadata (status codes, timestamps, destination hashed) rather than message content.
  • If sending anonymized telemetry to servers, ensure it cannot be re-identified and disclose this in your privacy policy.
  • Provide user controls to opt-out of telemetry.

Alternatives & when to choose them

  • Server-side SMS providers (Twilio, Nexmo, MessageBird): better deliverability, central sender number, analytics, compliance tools.
  • Firebase Cloud Messaging: for in-app notifications instead of SMS to reduce cost and privacy exposure.
  • SMS Retriever / User Consent APIs: for OTP flows without READ_SMS permission.

Comparison table:

Use case SmsManager (device) Server-side SMS
Central sender number No Yes
Deliverability & analytics Limited Strong
Per-message cost Uses user’s SMS plan Billed per message by provider
Privacy (user’s SIM used) Lower Higher control
Requires SEND_SMS permission Yes No (server sends)

Common pitfalls and how to avoid them

  • Requesting READ_SMS unnecessarily — avoid; use SMS Retriever for OTPs.
  • Sending messages without explicit user consent — get clear opt-in.
  • Assuming delivery receipts are reliable — treat them as best-effort.
  • Hardcoding phone numbers or sensitive data in code — keep config outside source and secure.

Sample end-to-end flow (concise)

  1. Ask for SEND_SMS at runtime with a clear rationale.
  2. Prepare content; if long, divide into parts.
  3. Use unique PendingIntents for sent/delivered callbacks.
  4. Handle result codes and implement retries/backoff.
  5. Log metadata only; store content encrypted if necessary.
  6. Provide UI transparency and opt-out.

Conclusion

SmsManager is powerful for device-originated SMS but requires careful handling of permissions, privacy, reliability, and platform differences. Favor user consent, minimize permissions, use delivery intents for tracking, and prefer server-based or specialized APIs (SMS Retriever, User Consent) when appropriate.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *