Add SMS one-time-code login with Esemes Gateway

Follow these steps in order. Esemes Gateway sends SMS through a real Android phone in Mauritius, so a mistake sends a real text to a real person.

  1. Read the docs first: the MCP tool get_docs if the esemes MCP server is connected, otherwise https://sms-admin.bizmakers.app/llms-full.txt (one file). API base: https://sms.bizmakers.app.
  2. Use a test key while building. Keys starting smt_ send nothing: results are simulated, POST /v1/otp returns test_code, number 5000 0001 fails, 5000 0002 expires, 5000 0003 is never delivered. Do not ask for or use a live key (smk_) until the user says to switch.
  3. Keep the key server-side. Put it in an environment variable (ESEMES_KEY). Never in browser JavaScript, a mobile app bundle, a public repo or a log line. If the app has no backend, add a small server endpoint; do not call the API from the client.
  4. Send: POST /v1/otp with {"to": "5123 4567", "purpose": "login"}. Store the returned otp_id in the server session (not the code, which you never receive outside test mode).
  5. Verify: POST /v1/otp/verify with {"otp_id", "code"}. Success is {"valid": true}. Treat any other result as a failed login and show a message for reason: incorrect (with attempts_left), expired_or_used (offer to resend), too_many_attempts (start over).
  6. Sign the person in properly after {"valid": true}. Use the app's existing auth/session if it has one. Otherwise issue a signed or encrypted session (e.g. iron-session or a jose JWT with a server secret). Never store the phone number in a plain cookie and trust it later: anyone can set that cookie by hand. Session and otp_id cookies are httpOnly, sameSite: "lax" and secure in production.
  7. Handle errors: 429 otp_cooldown = wait 30 s before resending (show a countdown); 429 recipient_rate_limited = too many codes for that number this hour; country_not_allowed = only Mauritian mobiles (+230 5xxx xxxx) work, say so; recipient_suppressed = the person opted out of this sender's messages (only affects non-OTP sends).
  8. Idempotency: for plain POST /v1/messages sends that may be retried, pass an idempotency_key.
  9. Webhooks (optional): verify X-SMS-Signature (t=<unix>,v1=<hex>, HMAC-SHA256 of "<t>." + rawBody) and reject timestamps older than 5 minutes.
  10. Test the whole flow with the test key, including a wrong code, the cooldown and a forged session cookie being refused, then tell the user what to change to go live (swap the key, nothing else).

The MCP server esemes-mcp has tools get_docs, send_sms, send_otp, verify_otp, get_message, list_messages, get_usage; it refuses a live key unless ESEMES_ALLOW_LIVE=1.