sms_sender_plus
sms_sender_plus is a Flutter plugin for sending SMS on Android and iOS.
It focuses on the parts Flutter apps commonly need:
- Android direct SMS sending with
SmsManager. - Android SIM-slot selection for dual-SIM devices.
- Android active SIM-card discovery.
- Android SMS and phone-state permission checks and requests.
- Android sent and delivered status events for single-recipient SMS.
- Android multi-recipient sending without delivery reports.
- iOS SMS composer support with
sent,cancelled, andfailedresults.
Platform Support
| Feature | Android | iOS |
|---|---|---|
| Send SMS | Direct send | System composer |
| Multiple recipients | Yes | Yes, through composer |
| Select SIM slot | Yes | No |
| Detect SIM cards | Yes | No |
| Sent status | Yes | Composer result |
| Delivery status | Single recipient only | No |
Installation
dependencies:
sms_sender_plus: ^0.2.0
Then run:
flutter pub get
Android Setup
Add these permissions to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Request permissions at runtime before calling the direct Android SMS APIs:
final hasSmsPermission = await SmsSenderPlus.instance.checkSmsPermission();
if (!hasSmsPermission) {
await SmsSenderPlus.instance.requestSmsPermission();
}
final hasPhoneStatePermission =
await SmsSenderPlus.instance.checkPhoneStatePermission();
if (!hasPhoneStatePermission) {
await SmsSenderPlus.instance.requestPhoneStatePermission();
}
SEND_SMS is required to send messages. READ_PHONE_STATE is required for
active SIM-card discovery and selecting a specific SIM slot on many Android
versions.
When no simSlot is provided, Android uses the device default SMS subscription
when one is available, including on phones with multiple active SIM cards.
iOS Setup
iOS sends through Apple's MFMessageComposeViewController.
Apple does not allow third-party apps to:
- Send SMS silently.
- Select a SIM card.
- Read SMS delivery status.
Because of that, simSlot and deliveryReport are ignored on iOS.
getSimCards() returns an empty list and getActiveSimCount() returns 0.
The permission methods return true on iOS because the system SMS composer
does not use Android-style runtime permission prompts.
Check And Request Permissions
final hasSmsPermission = await SmsSenderPlus.instance.checkSmsPermission();
final canSendSms = hasSmsPermission ||
await SmsSenderPlus.instance.requestSmsPermission();
final hasPhoneStatePermission =
await SmsSenderPlus.instance.checkPhoneStatePermission();
final canInspectSimCards = hasPhoneStatePermission ||
await SmsSenderPlus.instance.requestPhoneStatePermission();
Use SEND_SMS before direct Android SMS sending. Use READ_PHONE_STATE when
you need getSimCards() or want to force a specific simSlot.
Send One SMS
import 'package:sms_sender_plus/sms_sender_plus.dart';
final result = await SmsSenderPlus.instance.sendTextMessage(
recipient: '+989121234567',
message: 'Hello',
simSlot: 0,
deliveryReport: true,
);
print(result.state.name);
simSlot is zero-based. Use 0 for the first SIM slot and 1 for the second
SIM slot.
Send With The Default SIM
Omit simSlot to use the Android device default SMS SIM:
final result = await SmsSenderPlus.instance.sendTextMessage(
recipient: '+989121234567',
message: 'Hello from the default SIM',
);
Listen For Android Status Events
final subscription = SmsSenderPlus.instance.statusEvents.listen((event) {
print('${event.messageId}: ${event.status.name}');
});
For single-recipient Android messages, status events may include:
sentdeliveredfailed
On iOS, the status stream remains empty.
Send To Multiple Recipients
final result = await SmsSenderPlus.instance.sendTextMessages(
recipients: ['+989121234567', '+989351234567'],
message: 'Hello everyone',
simSlot: 1,
);
print(result.state.name);
Android batch sending does not request or emit delivery reports.
Get SIM Cards
final simCards = await SmsSenderPlus.instance.getSimCards();
for (final sim in simCards) {
print('slot ${sim.simSlotIndex}: ${sim.carrierName ?? sim.displayName}');
}
Each SimCard includes:
subscriptionIdsimSlotIndexdisplayNamecarrierNamenumbercountryIsoisEmbedded
Some fields may be null depending on Android version, device policy, carrier,
and granted permissions.
Check SMS Availability
final available = await SmsSenderPlus.instance.isSmsAvailable();
On Android this checks whether the device is SMS-capable. On iOS it calls
MFMessageComposeViewController.canSendText().
Errors
Expected native failures are converted to SmsSenderPlusException:
try {
await SmsSenderPlus.instance.sendTextMessage(
recipient: '+989121234567',
message: 'Hello',
);
} on SmsSenderPlusException catch (error) {
print('${error.code}: ${error.message}');
}
Common error codes:
invalidRecipientemptyRecipientsemptyMessagepermissionDeniedsimNotFoundsimSelectionRequiredsmsUnavailablesendFailedactivityUnavailableunsupportedPlatform
Manual Testing Checklist
Use a real device. Emulators and simulators usually cannot fully test SMS behavior.
- Android single-SIM send works after runtime permissions are granted.
- Android dual-SIM send works with
simSlot: 0. - Android dual-SIM send works with
simSlot: 1. - Android no-slot send uses the device default SMS SIM.
- Android single-recipient send emits
sentand, when available,delivered. - Android batch send sends all recipients and does not emit delivery events.
- iOS composer opens.
- iOS composer reports
sentwhen the user sends. - iOS composer reports
cancelledwhen the user cancels. - iOS reports
smsUnavailableon devices that cannot send SMS.