Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/fumadocs/content/docs/adapters/field-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ SMTP is built in and does not use Nodemailer. It maps address fields and headers

Unsupported fields fail fast. For example, Resend rejects `metadata`, Mailchimp Transactional rejects `replyTo`, and SMTP rejects attachments. That keeps production behavior boring: if a field matters, you find out before the provider request.

This behavior is covered by `packages/email-sdk/src/adapters.test.ts`. If an adapter starts mapping a new field, update the table and the payload test in the same change.

## Attachment rules

Attachment `content` is treated as raw content by default and encoded to Base64 for API adapters that require Base64.
Expand Down
4 changes: 0 additions & 4 deletions apps/fumadocs/content/docs/agents/meta.json

This file was deleted.

4 changes: 4 additions & 0 deletions apps/fumadocs/content/docs/concepts/adapter-model.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ console.log(adapter.raw);
```

The older `provider`, `defaultProvider`, `email.provider()`, and `email.withProvider()` names still work as aliases.

## Production rule

Choose fallbacks from adapters that can send the same required fields as the primary adapter. If the message includes attachments, metadata, tags, or custom headers, check <a href="/docs/adapters/field-support">field support</a> before adding a backup route.
2 changes: 2 additions & 0 deletions apps/fumadocs/content/docs/concepts/fallbacks-and-retries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const email = createEmailClient({

In this setup, Email SDK tries Resend first. If Resend fails with a retryable error, it retries once. If it still fails, it tries SMTP.

Do not use a fallback just because it can deliver email. Use a fallback when it can represent the fields your message needs. Unsupported fields fail fast before the provider request.

## Override fallback for one send

```ts
Expand Down
2 changes: 2 additions & 0 deletions apps/fumadocs/content/docs/concepts/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ Each hook receives:
## Keep logs safe

Do not log API keys, SMTP passwords, raw tokens, full message bodies, or unnecessary recipient data. For most production logs, routing name, status, subject category, template name, and message ID are enough.

Hook failures are swallowed. Email SDK treats hooks as observability callbacks, so a logging failure does not hide the provider result or provider error.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This repo includes an agent skill at:
skills/email-sdk/SKILL.md
```

Use it when an agent wires Email SDK into an app, reviews adapter setup, or updates these docs.
Use it when an agent wires Email SDK into an app, reviews adapter setup, updates provider docs, or checks production fallback behavior.

## What it tells agents

Expand All @@ -27,6 +27,7 @@ Use it when an agent wires Email SDK into an app, reviews adapter setup, or upda
```txt
Use the repo-local Email SDK skill at skills/email-sdk/SKILL.md.
Wire Resend as the primary adapter and SMTP as fallback.
Check /docs/adapters/field-support.md before choosing fallback routes.
Keep secrets in environment variables.
Add one narrow test around the send path.
```
Expand Down
45 changes: 45 additions & 0 deletions apps/fumadocs/content/docs/getting-started/ai-resources.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: AI resources
description: Use the Markdown docs, llms.txt, and agent skill when assistants work with Email SDK.
icon: BrainCircuit
---

Email SDK exposes machine-readable docs so coding assistants can retrieve the same setup, adapter, fallback, hook, and reference pages that humans read.

## Available resources

| Resource | What it is |
| -------------------------------------- | ----------------------------------------------------------------- |
| `/llms.txt` | A compact index of docs pages for assistants and AI search tools. |
| `/llms-full.txt` | One Markdown bundle containing every docs page. |
| `/docs/index.md` | Markdown for the docs landing page. |
| `/docs/getting-started/quickstart.md` | Markdown for a specific docs page. |
| `/docs/getting-started/agent-skill.md` | Agent instructions for Email SDK integration work. |

## Use per-page Markdown

Every docs page has a Markdown version. Add `.md` to the docs path:

```txt
/docs/adapters/resend.md
/docs/adapters/field-support.md
/docs/reference/client.md
```

Use per-page Markdown when an assistant only needs one topic. Use `/llms-full.txt` when it needs the complete docs bundle.

## Keep assistant answers grounded

When asking an assistant to wire Email SDK into an app, include the page that matches the job:

```txt
Use /docs/getting-started/provider-readiness.md and /docs/adapters/field-support.md.
Wire Resend as the primary adapter and SMTP as fallback.
Do not log API keys, SMTP passwords, raw tokens, or full message bodies.
```

For codebase work, also point the assistant at the repo-local skill:

```txt
Use skills/email-sdk/SKILL.md before changing Email SDK integration code.
```
2 changes: 1 addition & 1 deletion apps/fumadocs/content/docs/getting-started/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Getting started",
"pages": ["quickstart"]
"pages": ["quickstart", "provider-readiness", "ai-resources", "agent-skill"]
}
112 changes: 112 additions & 0 deletions apps/fumadocs/content/docs/getting-started/provider-readiness.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: Provider readiness
description: Choose production adapters, confirm field support, and validate fallback behavior before launch.
icon: ShieldCheck
---

Email SDK adapters are stable by contract: each adapter maps the `EmailMessage` fields it supports and rejects unsupported fields before calling the provider. That keeps production sends from silently dropping CC, BCC, reply-to, headers, tags, metadata, or attachments.

## Production checklist

1. Pick a primary adapter that supports every field your app sends.
2. Pick fallback adapters that support the same required fields.
3. Keep credentials in environment variables.
4. Add an idempotency key for sends that may be retried.
5. Use hooks for status and tracing, not secrets or full message bodies.
6. Run adapter payload tests before shipping provider changes.
7. Run a real provider smoke test only with explicit test recipients.

## Recommended pairs

| Need | Start with |
| -------------------------------- | -------------------------------------------------- |
| Most complete API field mapping | Postmark, SendGrid, Mailgun, Brevo |
| Resend-style DX with attachments | Resend |
| Cheap or self-managed delivery | SMTP |
| Product or event email | Loops, Plunk |
| Provider fallback for production | Resend plus SMTP, or one primary API plus Postmark |
| Attachment-heavy transactional | Resend, Postmark, SendGrid, Mailgun, MailerSend |

## Field support decides fallback safety

Fallbacks are only safe when the backup adapter can send the same kind of message. For example, a receipt with attachments should not fall back to SMTP in this SDK because SMTP currently rejects attachments. A basic text or HTML notification can fall back to SMTP because SMTP maps addresses, subject, text, HTML, reply-to, and headers.

Use the <a href="/docs/adapters/field-support">field support table</a> before choosing backup routes.

## Retry and fallback order

Retries happen inside one adapter. Fallbacks happen after that adapter has finished its retry loop.

```ts
const email = createEmailClient({
adapters: [
resend({ apiKey: process.env.RESEND_API_KEY! }),
smtp({
host: "smtp.purelymail.com",
port: 587,
auth: {
user: process.env.SMTP_USER!,
pass: process.env.SMTP_PASS!,
},
}),
],
retry: {
retries: 1,
},
fallback: ["smtp"],
});
```

With this setup, Email SDK tries Resend first. If Resend fails with a retryable error, it retries once. If the final Resend attempt still fails, Email SDK tries SMTP.

## Hooks are observability callbacks

Hooks receive the routing name, original message, attempt number, and optional `metadata` passed to `send`.

```ts
const email = createEmailClient({
adapters: [resend({ apiKey: process.env.RESEND_API_KEY! })],
hooks: {
beforeSend(event) {
console.log("email.send", event.provider, event.attempt);
},
afterSend(event) {
console.log("email.sent", event.provider, event.response.id);
},
onRetry(event) {
console.warn("email.retry", event.provider, event.nextAttempt);
},
onError(event) {
console.error("email.error", event.provider, event.error);
},
},
});
```

Hook failures are swallowed so observability code does not mask provider behavior.

## What is covered by tests

The package test suite covers:

| Area | Test file |
| --------------------------- | ----------------------------------------- |
| Provider payload mapping | `packages/email-sdk/src/adapters.test.ts` |
| Unsupported field rejection | `packages/email-sdk/src/adapters.test.ts` |
| Default adapter send path | `packages/email-sdk/src/core.test.ts` |
| Fallback routing | `packages/email-sdk/src/core.test.ts` |
| Legacy provider aliases | `packages/email-sdk/src/core.test.ts` |
| Retryable provider failures | `packages/email-sdk/src/core.test.ts` |
| Hook failure isolation | `packages/email-sdk/src/core.test.ts` |

Run the narrow production gate:

```bash
bun test packages/email-sdk/src/adapters.test.ts packages/email-sdk/src/core.test.ts
```

Then run the package build:

```bash
bun run --filter email-sdk build
```
6 changes: 6 additions & 0 deletions apps/fumadocs/content/docs/getting-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export const email = createEmailClient({
});
```

Before using a fallback in production, confirm the backup adapter supports every field your message sends. See <a href="/docs/getting-started/provider-readiness">provider readiness</a> and <a href="/docs/adapters/field-support">field support</a>.

Your application still sends the same way:

```ts
Expand Down Expand Up @@ -95,3 +97,7 @@ email-sdk send \
--subject "Hello" \
--text "It works"
```

## Agent skill

When a coding assistant wires Email SDK into an app, point it at <a href="/docs/getting-started/agent-skill">Agent skill</a>. The skill tells agents to use adapter entry points, keep secrets in environment variables, avoid Nodemailer for SMTP, and validate fallback compatibility.
12 changes: 6 additions & 6 deletions apps/fumadocs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ await email.send({
/>
<Card
title="Understand adapters"
href="/docs/concepts/adapter-model"
description="Learn how adapters and routing names work."
href="/docs/getting-started/provider-readiness"
description="Pick production-safe adapters, fallbacks, retries, and hooks."
/>
<Card
title="Browse adapters"
href="/docs/adapters"
description="See all supported email services."
/>
<Card
title="Read the API reference"
href="/docs/reference/client"
description="Look up client options, messages, errors, and the CLI."
title="AI resources"
href="/docs/getting-started/ai-resources"
description="Use llms.txt, per-page Markdown, and the agent skill."
/>
</Cards>

Expand All @@ -57,7 +57,7 @@ await email.send({
- Fifteen adapters, including Resend, SMTP, Postmark, SendGrid, Mailgun, and MailerSend.
- Retry, fallback, and hook options.
- A small CLI for adapter setup checks and test sends.
- A repo-local agent skill for future coding agents.
- Markdown docs, `llms.txt`, and a repo-local agent skill for coding assistants.

## What stays small

Expand Down
2 changes: 1 addition & 1 deletion apps/fumadocs/content/docs/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Email SDK",
"pages": ["index", "getting-started", "concepts", "adapters", "reference", "agents"]
"pages": ["index", "getting-started", "concepts", "adapters", "reference"]
}
4 changes: 4 additions & 0 deletions apps/fumadocs/src/lib/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export const appName = "Email SDK";
export const siteDescription =
"A lightweight TypeScript SDK for unified email sending with Resend, SMTP, Postmark, fallbacks, hooks, and a Bun CLI.";
const env = typeof process === "undefined" ? {} : process.env;
export const siteUrl = env.SITE_URL ?? env.VITE_SITE_URL ?? "https://email-sdk.dev";
export const docsRoute = "/docs";
export const docsImageRoute = "/og/docs";

Expand Down
Loading