SmsGora

Hex.pm Docs tests

Elixir client for the SMS Gora SMS Gateway API.

SMS Gora turns Android phones with ordinary SIM cards into an SMS gateway. Your server calls the API, a linked phone sends the text, and replies and delivery reports come back to you. There's no sender ID registration and no per-message fee from us: your mobile operator bills the texts like any SMS.

Installation

Add smsgora to your dependencies in mix.exs:

def deps do
  [
    {:smsgora, "~> 1.0"}
  ]
end

Quick start

Create an API key in the SMS Gora dashboard (SMS Gora -> API keys) and keep it on your server.

client = SmsGora.new(System.fetch_env!("SMSGORA_KEY"))

{:ok, %{"message_ids" => [id]}} =
  SmsGora.send_sms(client, "+15551234567", "Your verification code is 482913")

Sending

# Several recipients (up to 1,000 per call)
SmsGora.send_sms(client, ["+15551234567", "+447700900123"], "Your order has shipped")

# From a chosen phone and SIM, scheduled for later
SmsGora.send_sms(client, "+15551234567", "Reminder: your appointment is tomorrow at 10:00",
  device_id: 42,
  sim_slot: 1, # 0 = SIM 1, 1 = SIM 2
  schedule_at: ~U[2026-12-01 08:00:00Z]
)

# Different texts for different people in one call (up to 5,000 recipients)
SmsGora.send_bulk_sms(client, 42, [
  %{recipients: ["+15551234567"], message: "Hi Aline, parcel #1042 is ready."},
  %{recipients: ["+15557654321"]}
], message_template: "Hello, your parcel is ready for pickup.")

Delivery status and incoming SMS

{:ok, msg} = SmsGora.get_message(client, 1873)
msg["status"] # queued -> dispatched -> sent -> delivered (or failed)

SmsGora.cancel_message(client, 1874) # works while queued or scheduled

{:ok, inbox} = SmsGora.list_received_sms(client, since: "2026-11-01", limit: 50)
{:ok, phones} = SmsGora.list_devices(client)
{:ok, account} = SmsGora.get_account(client)

Anything else in the API reference is one call away:

SmsGora.request(client, :get, "/batches/311")

Webhooks

Add your HTTPS URL in SMS Gora -> Webhooks (or with create_webhook/3) and keep the secret. Each request carries X-SmsGora-Signature: sha256=..., an HMAC-SHA256 of the raw body. In Phoenix, keep the raw body with a body_reader in Plug.Parsers, then:

def sms_webhook(conn, params) do
  signature = conn |> get_req_header("x-smsgora-signature") |> List.first()

  if SmsGora.verify_webhook(conn.assigns.raw_body, signature, System.fetch_env!("SMSGORA_WEBHOOK_SECRET")) do
    if params["event"] == "sms.received" do
      # params["data"]["from"], params["data"]["message"]: enqueue an Oban job
    end

    send_resp(conn, 200, "ok") # answer within 10 seconds
  else
    send_resp(conn, 401, "bad signature")
  end
end

Errors

Failed calls return {:error, %SmsGora.Error{}} with status, message, field errors and, on 429, retry_after in seconds.

case SmsGora.send_sms(client, "+15551234567", "Hi") do
  {:ok, result} -> result
  {:error, %SmsGora.Error{status: 429, retry_after: wait}} -> Process.sleep(trunc((wait || 5) * 1000))
  {:error, error} -> raise error
end

Never put the key in a browser or mobile app: anyone could read it. Call the API from your server.

Migrating from TextBee

SMS Gora accepts TextBee-style requests, so most integrations switch by changing the base URL and key. See the compatibility notes.

More from Tekservix

License

MIT