SMS composer for Mob apps. Opens the user's default messaging app pre-filled with recipient + body; the user taps Send to actually send. No dangerous permissions on either platform. Cross-platform surface (MobSms.compose/2), per-platform delivery asymmetry documented honestly.

Install

Requires mob 0.9.0 or newer.

def deps do
  [
    {:mob,     "~> 0.9.0"},
    {:mob_sms, "~> 0.1"}
  ]
end

In mob.exs, activate the plugin:

config :mob, :plugins, [:mob_sms]

Usage

MobSms.compose(socket,
  to: "+15551234567",
  body: "Join me on Sample App: https://example.com/i/ABC123"
)

Both :to and :body are optional. Omit :to to let the user pick a recipient from Contacts inside the composer.

Result arrives as a message to the calling process:

def handle_info({:sms, :sent},            socket), do: ...  # iOS
def handle_info({:sms, :cancelled},       socket), do: ...  # iOS
def handle_info({:sms, :composer_opened}, socket), do: ...  # Android
def handle_info({:sms, :not_available},   socket), do: ...  # both

Per-platform delivery — the asymmetry to know

  • iOS — MFMessageComposeViewController is presented as a sheet inside your app. The system delegate reports :sent, :cancelled, or :failed reliably. :not_available means the device cannot send SMS (simulator, no cellular / iMessage capability, no SIM).

  • Android — Intent.ACTION_SENDTO with smsto: opens the user's default SMS app externally. Android does not reliably tell the host app whether the user tapped Send or cancelled — most SMS apps do not set an activity result. The plugin delivers :composer_opened once the composer is showing (or :not_available if no SMS-capable app is installed) and stops there. If knowing what the user did afterwards matters to your flow, plan around this asymmetry rather than assuming a :sent.

This asymmetry is by design at the platform layer, not something the plugin can paper over. Android intentionally hands the user off to a separate app rather than embedding a sheet; iOS presents inline and can observe the outcome.

Common use cases

  • Invite codes — "Join me on X, here's the link" — the canonical fit
  • Share a link / share a file — user picks a friend inside the composer
  • Tell a friend — referral flows without your app needing to know the friend's number
  • Verification workaround — send a code to a manually-provided number without SMS gateway costs

What this plugin is NOT

Not silent-send. iOS has no public API for sending SMS without the user tapping Send; Apple treats that as a bright-line spam surface. Android can silent-send via SmsManager.sendTextMessage under the SEND_SMS runtime permission, but Google Play rejects that permission request for most non-SMS-centric apps (the "Permissions Declaration" gate).

If you have a real reason to bypass the sheet on Android — and can accept iOS being permanently in composer mode — a separate send/2 on this module can land in a later release. Talk to us before designing around it.

  • MOB epic MOB-257 — the mob_wake plugin arc, which covers the receive side of push notifications for apps that want the "wake me when something happens" flow that people often reach for background tasks to build.

License

MIT.