> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bosonware.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> How the BosonMail SDK reports errors through result objects. Learn to classify constructor, validation, API, network, and timeout failures.

The BosonMail SDK uses a result-object error model. Every method resolves to an object that tells you whether the operation succeeded and, if not, why it failed. The constructor is the only place the SDK throws; everything else returns a result you inspect programmatically.

## The result-object model

There are two result types:

* **`Result`** from `sendEmail`
* **`EmailStatusResult`** from `getEmailStatus`

Both share `success`, `errorMessage`, and `statusCode`. `EmailStatusResult` adds `errorCode`, which is useful for programmatic handling of status lookup failures.

### Result fields

**success** `boolean` *required*

`true` for success, `false` for any failure.

**errorMessage** `string`

Human-readable description of what went wrong.

**statusCode** `number`

HTTP status code from the API response. Absent for client-side validation errors and network/timeout failures.

**errorCode** `string`

Machine-readable error code. Present only on `EmailStatusResult`.

## Error categories

### 1. Constructor errors (thrown)

The constructor throws `Error` with a descriptive message if any option is invalid. These are the only synchronous throws in the SDK.

| Condition | Thrown message |
| - | - |
| `apiKey` missing or empty after trim | `'apiKey' must be a non-empty string` |
| `timeoutMs` not a finite number > 0 | `'timeoutMs' must be a positive number` |
| `baseUrl` empty after trim | `'baseUrl' must be a non-empty URL` |
| `baseUrl` not absolute HTTP(S) | `'baseUrl' must be an absolute HTTP(S) URL` |

### 2. Client-side validation (no statusCode)

Before any network request, the SDK validates input. These return `{ success: false, errorMessage }` with no `statusCode`:

| Method | Exact error message |
| - | - |
| `sendEmail` | `'to' must contain at least one recipient` |
| `sendEmail` | `'subject' must be a non-empty string` |
| `sendEmail` | `'body' must be an object` |
| `sendEmail` | `'text' and 'html' must be strings` |
| `sendEmail` | `Email must contain text or HTML content` |
| `sendEmail` | `'attachments' must be an array` |
| `sendEmail` | `A maximum of 10 attachments is allowed` |
| `sendEmail` | `Each attachment must have a filename` |
| `sendEmail` | `Invalid content for attachment "<filename>"` |
| `sendEmail` | `Attachment "<filename>" exceeds the 10 MB per-attachment limit` |
| `sendEmail` | `Total attachment size exceeds the 20 MB limit` |
| `sendEmail` | `Failed to prepare email: <message>` |
| `getEmailStatus` | `'messageId' must be a non-empty string` (errorCode: `invalid_id`) |

### 3. API errors (statusCode present)

When the API returns a non-2xx response or `success !== true`, the SDK extracts the error from the response body and returns:

```typescript theme={null}
{
  success: false,
  statusCode: 401,
  errorMessage: "Unauthorized"
}
```

The error message is resolved from `body.error` (string), `body.error.message` (object), `body.message`, or a fallback `Request failed (HTTP <status> <statusText>)`.

### 4. Network and timeout (no statusCode)

When `fetch` fails or the request times out, the method resolves with:

```typescript theme={null}
{
  success: false,
  errorMessage: "Request timed out after 30000ms. The outcome may be unknown; check email status before retrying."
}
```

Network errors (not timeouts) also lack `statusCode` and contain the underlying error message.

> [!WARNING]
>
> A timeout is ambiguous. The server may have accepted the email even though the client never received the response. Before retrying, check the email status if you have a `messageId`, or log the timeout for manual reconciliation. Blind retries risk duplicates.

## Error codes on EmailStatusResult

The `errorCode` field helps you decide how to respond programmatically:

| Code | Source | Meaning |
| - | - | - |
| `invalid_id` | SDK | `messageId` was empty or not a string |
| `request_failed` | SDK | Network error or timeout |
| `invalid_response` | SDK | API response was missing required fields (`id`, `status`, `delivery_status`) |
| `unauthorized` | API | Authentication missing or invalid |
| `not_found` | API | Email could not be found |
| `http_<status>` | SDK fallback | API returned no error code; `<status>` is the HTTP status code |

## Classifying a Result

You can write a helper that categorizes any `Result` or `EmailStatusResult`:

```typescript classify-error.ts theme={null}
import type { Result, EmailStatusResult } from "@bosonware-ent/bosonmail";

type ErrorCategory = "validation" | "api" | "network" | "timeout";

function classifyError(result: Result | EmailStatusResult): ErrorCategory {
  if (result.statusCode === undefined) {
    if (result.errorMessage?.startsWith("Request timed out")) {
      return "timeout";
    }
    return "network";
  }

  if ("errorCode" in result && result.errorCode === "invalid_id") {
    return "validation";
  }

  // Client-side validation errors have no statusCode and specific messages
  const validationMessages = [
    "'to' must contain at least one recipient",
    "'subject' must be a non-empty string",
    "'body' must be an object",
    "'text' and 'html' must be strings",
    "Email must contain text or HTML content",
    "'attachments' must be an array",
    "A maximum of 10 attachments is allowed",
    "Each attachment must have a filename",
    "Failed to prepare email",
  ];

  if (validationMessages.some(m => result.errorMessage?.startsWith(m))) {
    return "validation";
  }

  return "api";
}
```

## Retry guidance

The SDK has no built-in retries. Implement retry logic in your application when it makes sense:

* **Retry**: No `statusCode` due to a non-timeout network error, or `statusCode >= 500`, or `statusCode === 429`.
* **Do not retry**: Client-side validation errors (`statusCode` absent and specific message), `4xx` errors other than `429`, timeouts (check status or log instead).
* **Timeout caution**: `sendEmail` on timeout returns no `messageId`, so you cannot look up the status. Log the timeout and reconcile manually, or accept a duplicate risk explicitly before retrying.

```typescript retry-helper.ts theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";
import type { Result } from "@bosonware-ent/bosonmail";

const client = new BosonMailClient({ apiKey: process.env.BOSONMAIL_API_KEY! });

async function sendWithRetry(
  to: string,
  subject: string,
  body: { text: string },
  maxRetries = 3,
  baseDelayMs = 500
): Promise<Result> {
  let lastResult: Result;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    lastResult = await client.sendEmail(to, subject, body);

    if (lastResult.success) return lastResult;

    // Do not retry validation errors
    const isValidation = !lastResult.statusCode &&
      !lastResult.errorMessage?.startsWith("Request timed out");
    if (isValidation) return lastResult;

    // Do not retry timeouts blindly
    if (lastResult.errorMessage?.startsWith("Request timed out")) {
      console.warn("Timeout on attempt", attempt + 1, "- reconcile before retrying");
      return lastResult;
    }

    // Retry only API errors >= 500 or 429
    const shouldRetry = lastResult.statusCode === undefined ||
      lastResult.statusCode >= 500 ||
      lastResult.statusCode === 429;

    if (!shouldRetry || attempt === maxRetries) return lastResult;

    const delay = baseDelayMs * 2 ** attempt;
    await new Promise(r => setTimeout(r, delay));
  }

  return lastResult!;
}
```

> [!NOTE]
>
> The helper above is application code, not an SDK feature. Adapt the retry strategy to your application's reliability requirements and observability setup.

## Converting results to exceptions

If you prefer a throw-style API, wrap the SDK calls:

```typescript throw-style.ts theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";
import type { Result, EmailStatusResult } from "@bosonware-ent/bosonmail";

class BosonMailError extends Error {
  constructor(
    message: string,
    public readonly statusCode?: number,
    public readonly errorCode?: string
  ) {
    super(message);
  }
}

function assertResult(result: Result): asserts result is Result & { success: true } {
  if (!result.success) {
    throw new BosonMailError(result.errorMessage ?? "Unknown error", result.statusCode);
  }
}

function assertStatusResult(result: EmailStatusResult): asserts result is EmailStatusResult & { success: true } {
  if (!result.success) {
    throw new BosonMailError(
      result.errorMessage ?? "Unknown error",
      result.statusCode,
      result.errorCode
    );
  }
}

// Usage
const client = new BosonMailClient({ apiKey: process.env.BOSONMAIL_API_KEY! });

const result = await client.sendEmail("user@example.com", "Hello", { text: "Hi there" });
assertResult(result);
console.log("Sent:", result.messageId);
```

## JavaScript example

```javascript error-handling.mjs theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";

const client = new BosonMailClient({ apiKey: process.env.BOSONMAIL_API_KEY });

const result = await client.sendEmail(
  "user@example.com",
  "Welcome",
  { text: "Welcome to our platform." }
);

if (!result.success) {
  if (result.statusCode === undefined) {
    if (result.errorMessage?.startsWith("Request timed out")) {
      console.error("Timeout - check status before retrying");
    } else {
      console.error("Validation or network error:", result.errorMessage);
    }
  } else if (result.statusCode >= 500) {
    console.error("Server error:", result.statusCode, result.errorMessage);
  } else {
    console.error("Client or API error:", result.statusCode, result.errorMessage);
  }
} else {
  console.log("Sent:", result.messageId);
}
```

> [card] [Sending emails](https://docs.bosonware.com/sdk/sending-emails.md)
>
> Full sendEmail reference with all validation rules and error messages.

> [card] [Email status](https://docs.bosonware.com/sdk/email-status.md)
>
> How getEmailStatus reports errors with errorCode values.

> [card] [Client configuration](https://docs.bosonware.com/sdk/client-configuration.md)
>
> Timeout settings that affect network and timeout errors.

> [card] [Guides: Production best practices](https://docs.bosonware.com/guides/production-best-practices.md)
>
> Logging, monitoring, and retry strategies for production deployments.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.