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, and failed results.

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:

  • sent
  • delivered
  • failed

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:

  • subscriptionId
  • simSlotIndex
  • displayName
  • carrierName
  • number
  • countryIso
  • isEmbedded

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:

  • invalidRecipient
  • emptyRecipients
  • emptyMessage
  • permissionDenied
  • simNotFound
  • simSelectionRequired
  • smsUnavailable
  • sendFailed
  • activityUnavailable
  • unsupportedPlatform

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 sent and, when available, delivered.
  • Android batch send sends all recipients and does not emit delivery events.
  • iOS composer opens.
  • iOS composer reports sent when the user sends.
  • iOS composer reports cancelled when the user cancels.
  • iOS reports smsUnavailable on devices that cannot send SMS.

Libraries

sms_sender_plus