{
  "slug": "otp-sms-email-fallback",
  "title": "OTP by SMS with email fallback: template and code",
  "url": "https://www.courier.com/recipes/otp-sms-email-fallback",
  "markdown": "https://www.courier.com/recipes/otp-sms-email-fallback.md",
  "stage": "Account and security",
  "purpose": "Send a login code by SMS, fall back to email, and stop retrying once the code is stale.",
  "summary": "An OTP by SMS with email fallback sends a one-time login code by text message, or by email when the user has no phone number. The code is good for 10 minutes, and Courier stops trying to deliver it after that, so nobody receives a code that's already expired.",
  "channels": [
    "sms",
    "email"
  ],
  "shape": "send",
  "shows": [
    "Channel fallback in one send call",
    "A retry window that matches the code's lifetime",
    "One template with SMS and email versions"
  ],
  "data": [
    {
      "name": "code",
      "type": "string",
      "example": "482913"
    }
  ],
  "steps": [
    {
      "step": "1",
      "when": "When someone signs in",
      "channels": "SMS, or email when there's no phone number",
      "templates": [
        "templates/login-code.json"
      ]
    }
  ],
  "files": {
    "templates": [
      "templates/login-code.json"
    ],
    "setup": [
      "setup.ts"
    ],
    "start": [
      "start.ts",
      "start.py"
    ]
  },
  "docs": [
    "https://www.courier.com/docs/send/routing",
    "https://www.courier.com/docs/send/overview"
  ],
  "related_recipes": [
    "https://www.courier.com/recipes/magic-link-email",
    "https://www.courier.com/recipes/password-reset-email",
    "https://www.courier.com/recipes/new-device-login-alert"
  ],
  "agent_prompt": "Add login codes to this app, sent through Courier: SMS first, email when there's no phone number, and no retries after 10 minutes. Follow the steps at https://www.courier.com/recipes/otp-sms-email-fallback.md and use the Courier skill for SDK details.\n\n1. Create the template in my Courier workspace by running setup.ts from that page with COURIER_API_KEY from our environment. Save the template ID it prints as COURIER_LOGIN_CODE_TEMPLATE_ID.\n2. Find where this codebase generates a login or verification code and send it there with the recipe's send code. Pass only the code. Replace the placeholder product name (Acme) in the template with ours, and make its \"10 minutes\" match the code's real lifetime and the send timeout.\n3. Match this codebase's language, file layout, and error handling. Never hardcode the API key.\n4. When you're done, send one code to a test user and show me which channel it went out on.",
  "status": {
    "state": "passing",
    "last_tested": "2026-10-19"
  }
}
