> ## 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.

# Production best practices

> Secure, monitor, and scale your BosonMail integration with key management, retries, logging, and endpoint protection.

This guide is a checklist for running BosonMail in production. It covers key management, client configuration, error handling, logging, retries, queueing, endpoint security, and version pinning. Follow these practices to keep your integration reliable and your credentials safe.

## Key management and rotation

Your API key grants access to your BosonMail account. Protect it as you would any production secret.

> [!TIP]
>
> Store the key in an environment variable or a secrets manager. Never commit it to version control.

> [!TIP]
>
> Load the key at runtime and fail fast if it is missing.

> [!TIP]
>
> Do not expose the key in browser code, frontend bundles, or client-side logs.

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

const apiKey = process.env.BOSONMAIL_API_KEY;
if (!apiKey) {
  throw new Error("BOSONMAIL_API_KEY is not set");
}
export const bosonmail = new BosonMailClient({ apiKey });
```

> [!WARNING]
>
> The BosonMail SDK stores the API key on `client.apiKey` as a public readonly property. Avoid logging the client object directly, because that will emit the key into logs.

Key rotation procedures are not yet documented by BosonWare.

> [!NOTE]
>
> Not yet documented by BosonWare: how to create, rotate, or revoke API keys, and the full list of available scopes. Contact [support@bosonware.com](mailto:support@bosonware.com) for guidance.

## Single client instance

Instantiate `BosonMailClient` once and reuse it across your application. The client is stateless and safe to share between requests.

```typescript theme={null}
// Do this once at module load
export const bosonmail = new BosonMailClient({ apiKey });

// Do not create a new client inside every request handler
```

## Timeouts

The SDK defaults to 30 seconds. Tune `timeoutMs` to match your infrastructure and user expectations.

```typescript theme={null}
const client = new BosonMailClient({
  apiKey,
  timeoutMs: 10_000, // 10 seconds for fast feedback
});
```

A timeout during sending can leave the outcome uncertain: the server may have accepted the message even though the client did not receive the response. Check email status before retrying where possible, or accept the risk of duplicate messages explicitly.

## Never-throwing assumption

The SDK constructor is the only place it throws. Methods always resolve, even on validation, network, or timeout failures. Always check `result.success` before using `result.id` or `result.messageId`.

```typescript theme={null}
const result = await client.sendEmail(to, subject, { text });
if (!result.success) {
  // Handle failure
  console.error("Send failed:", result.errorMessage, "statusCode:", result.statusCode);
  return;
}
// Safe to use result.id and result.messageId
```

## Logging without secrets or PII

Redact sensitive fields from logs. Do not log the client object, raw email content, or recipient lists at info level.

```typescript theme={null}
function logSend(result: { success: boolean; id?: string; messageId?: string; statusCode?: number; errorMessage?: string }, recipientHint: string) {
  if (result.success) {
    console.log("Email accepted", { id: result.id, messageId: result.messageId, statusCode: result.statusCode, recipient: recipientHint });
  } else {
    console.error("Email rejected", { error: result.errorMessage, statusCode: result.statusCode, recipient: recipientHint });
  }
}
```

## Persist message IDs

Store `result.messageId` in your database so you can check status later. The SDK does not poll automatically, and a timeout gives you no message ID to query.

```typescript theme={null}
const result = await client.sendEmail(to, subject, { text });
if (result.success && result.messageId) {
  await db.emails.create({
    data: {
      messageId: result.messageId,
      recipient: to,
      subject,
      status: result.status ?? "queued",
      sentAt: new Date(),
    },
  });
}
```

## Status reconciliation

Poll `getEmailStatus` periodically for messages that have not reached a terminal state. The README treats `delivered`, `failed`, `bounced`, and `rejected` as terminal. Do not treat `deferred` or `sent` as terminal.

```typescript theme={null}
async function pollStatus(messageId: string, maxAttempts = 6) {
  for (let i = 0; i < maxAttempts; i++) {
    const result = await client.getEmailStatus(messageId);
    if (!result.success) {
      console.error("Status check failed:", result.errorMessage);
      break;
    }
    const status = result.data?.status ?? "unknown";
    const terminal = ["delivered", "failed", "bounced", "rejected"];
    if (terminal.includes(status)) {
      console.log("Terminal status:", status);
      break;
    }
    await new Promise((r) => setTimeout(r, 5000));
  }
}
```

> [!WARNING]
>
> Do not run tight polling loops inside user-facing request handlers. Use a background worker, queue, or Cron Trigger instead.

## Retry policy

The SDK does not retry automatically. Implement retries in your application only for transient failures.

> [accordion] When to retry
>
> Retry only when there is no `statusCode` due to a non-timeout network error, or when `statusCode` is 500 or greater, or 429. Do not retry 4xx validation errors.

> [accordion] When not to retry
>
> Do not blindly retry after a timeout. The SDK returns no `messageId` on timeout, so you cannot look up the status. Log the failure and reconcile manually, or accept a duplicate risk explicitly.

```typescript theme={null}
async function sendWithRetry(to: string, subject: string, body: { text?: string; html?: string }) {
  const maxAttempts = 3;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const result = await client.sendEmail(to, subject, body);
    if (result.success) return result;

    const isRetryable =
      result.statusCode === undefined ||
      result.statusCode >= 500 ||
      result.statusCode === 429;

    if (!isRetryable || attempt === maxAttempts) {
      return result;
    }

    const delay = Math.min(1000 * 2 ** attempt, 8000);
    await new Promise((r) => setTimeout(r, delay));
  }
  throw new Error("Unreachable");
}
```

## Queueing sends

For user-facing latency, do not block the HTTP response while waiting for BosonMail. Push send jobs to a background queue and return immediately.

```typescript theme={null}
// Pseudocode for any job queue
await queue.add("send-email", {
  to,
  subject,
  text,
  html,
  messageId: result.messageId,
});
```

## Protect your send endpoints

An unprotected send endpoint is an open relay. Secure it before going live.

> [!TIP]
>
> Require authentication on every send request.

> [!TIP]
>
> Add rate limiting per user or IP.

> [!TIP]
>
> For public contact forms, use a CAPTCHA and a strict recipient allowlist.

> [!TIP]
>
> Validate and sanitize all inputs before passing them to the SDK.

## HTML escaping

If you render user-generated content into HTML email bodies, escape it to prevent injection. Use a trusted library or framework auto-escaping.

```typescript theme={null}
import { escapeHtml } from "your-html-escaper";

const html = `<p>Hello ${escapeHtml(name)},</p><p>${escapeHtml(message)}</p>`;
```

## Attachment validation

The SDK validates attachment size and count, but you should also validate files in your own middleware:

* Reject unexpected file types at the application layer.
* Enforce per-file and total size limits before the SDK sees the data.
* Scan uploads for malware if your use case requires it.

## Monitor outbound allowance

Each BosonMail inbox includes 500 outbound emails per month. Emails sent through the SDK likely count against this allowance, but confirm this with BosonWare. Track your volume and set alerts before you approach the limit.

> [!NOTE]
>
> BosonWare has not confirmed whether SDK/API sends count against the 500 outbound emails per inbox per month. Contact [support@bosonware.com](mailto:support@bosonware.com) to verify.

## Pin SDK versions

The BosonMail SDK is pre-1.0. Minor version bumps may change behavior. For example, 0.2.1 changed JSON request serialization for `to`, `cc`, and `bcc`. Pin the exact version in `package.json` and review the changelog before upgrading.

```json package.json theme={null}
{
  "dependencies": {
    "@bosonware-ent/bosonmail": "0.2.1"
  }
}
```

## Testing with dependency injection

Make the client injectable so tests can substitute a stub without touching the network.

```typescript theme={null}
export async function sendWelcomeEmail(
  client: BosonMailClient,
  to: string,
  name: string
) {
  return client.sendEmail(to, "Welcome", {
    text: `Hi ${name}, welcome to our service.`,
    html: `<p>Hi ${name}, welcome to our service.</p>`,
  });
}
```

In tests, pass a mock client or override `sendEmail` on a real client configured with a test `baseUrl`.

## Related

> [card] [Error handling](https://docs.bosonware.com/sdk/error-handling.md)
>
> SDK error codes, validation failures, and timeout behavior.

> [card] [Email status](https://docs.bosonware.com/sdk/email-status.md)
>
> How to check delivery status with getEmailStatus.

> [card] [Security](https://docs.bosonware.com/advanced/security.md)
>
> Security considerations for API keys and data handling.

> [card] [Rate limits](https://docs.bosonware.com/advanced/rate-limits.md)
>
> What is known and unknown about API rate limiting.


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