{"record":{"id":"0277ec4d4e3355a0","repo":"antiwork/gumroad","slug":"something-went-wrong-0277ec","errorCode":null,"errorMessage":"Something went wrong.","messagePattern":"Something went wrong\\.","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/components/Download/FileList.tsx","lineNumber":812,"sourceCode":"  token: string;\n  fileId: string;\n  email: null | string;\n  onDone: () => void;\n}) => {\n  const [emailEntry, setEmailEntry] = React.useState<string>(email || \"\");\n  const [hasError, setHasError] = React.useState(false);\n\n  const sendToKindle = async () => {\n    try {\n      const response = await request({\n        url: Routes.send_to_kindle_path(token),\n        method: \"POST\",\n        accept: \"json\",\n        data: { email: emailEntry, file_external_id: fileId },\n      });\n\n      const json = typia.assert<{ success: boolean; error?: string }>(await response.json());\n      if (!json.success) throw new ResponseError(json.error ?? \"Something went wrong.\");\n\n      showAlert(\"It's been sent to your Kindle.\", \"success\");\n      onDone();\n    } catch (e) {\n      assertResponseError(e);\n      showAlert(e.message, \"error\");\n      setHasError(true);\n    }\n  };\n\n  return (\n    <div>\n      <div className=\"flex gap-2\">\n        <Fieldset className=\"flex-1\" state={hasError ? \"danger\" : undefined}>\n          <Input\n            type=\"text\"\n            value={emailEntry}\n            onChange={(evt) => {","sourceCodeStart":794,"sourceCodeEnd":830,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/components/Download/FileList.tsx#L794-L830","documentation":"sendToKindle POSTs { email, file_external_id } to send_to_kindle_path. The server answers 200 with { success, error } and the throw fires when success is false, preferring the server's error string and falling back to 'Something went wrong.'. The catch uses assertResponseError, which re-throws anything that is not a ResponseError — so a TypiaError from the shape assert on a non-JSON/HTML response body would escape to the error boundary rather than the alert.","triggerScenarios":"Invalid or undeliverable Kindle email (user typo, not an @kindle.com address); file type/size Amazon's conversion service rejects; purchase token expired; SMTP/delivery failure recorded server-side — all return success:false with an error message that becomes the alert text.","commonSituations":"Buyer typo in the Kindle email; sending a DRM'd or oversized file Amazon refuses; personal-document quota on the Kindle account exceeded; token expiry on old download pages.","solutions":["Read the alert text — it is the server's own error message and usually names the exact problem (bad email, unsupported file, delivery failure).","Validate the email format client-side before submitting (see defense).","If the message is generic, check server logs for the Send-to-Kindle delivery attempt.","Confirm the purchase token is still valid (same checks as any download action).","For repeated delivery failures, suggest the user check their Amazon approved-sender list."],"exampleFix":"// before\nconst json = typia.assert<{ success: boolean; error?: string }>(await response.json());\nif (!json.success) throw new ResponseError(json.error ?? 'Something went wrong.');\n\n// after — same behavior, but keep non-ResponseError shape failures inside the alert path too\nlet json: { success: boolean; error?: string };\ntry {\n  json = typia.assert<{ success: boolean; error?: string }>(await response.json());\n} catch {\n  throw new ResponseError('Something went wrong.');\n}\nif (!json.success) throw new ResponseError(json.error ?? 'Something went wrong.');","handlingStrategy":"validation","validationCode":"const KINDLE_EMAIL = /^[^\\s@]+@[^\\s@]+\\.kindle\\.com$/i; // or the country variants\n\nif (!KINDLE_EMAIL.test(emailEntry.trim())) {\n  setHasError(true);\n  showAlert('Enter a valid Send-to-Kindle email address (e.g. you@kindle.com).', 'error');\n  return;\n}","typeGuard":"const isResponseError = (e: unknown): e is ResponseError => e instanceof ResponseError;","tryCatchPattern":"try {\n  await sendToKindle();\n} catch (e) {\n  assertResponseError(e); // TypiaError escapes — keep the JSON parse inside a ResponseError wrapper\n  showAlert(e.message, 'error'); // usually the server's delivery-failure reason\n}","preventionTips":["Validate the Kindle email format client-side before the POST — typos are the top cause.","Show the server's error text verbatim; it names whether it was the address or the delivery.","Wrap the typia assert so an HTML error page becomes a ResponseError instead of escaping via assertResponseError.","Rate-limit the send button: repeated clicks both spam the endpoint and duplicate deliveries."],"tags":["http","kindle","email-delivery","api-error-body"],"backgroundTag":"api-error-response-body","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}