mirror of
https://github.com/zoriya/drizzle-otel.git
synced 2025-12-06 00:46:09 +00:00
281 lines
9.0 KiB
Markdown
281 lines
9.0 KiB
Markdown
# @kubiks/otel-inbound
|
|
|
|
OpenTelemetry instrumentation for the [Inbound](https://inbound.new) email API SDK.
|
|
Capture spans for every Inbound API operation, enrich them with detailed metadata,
|
|
and monitor your complete email workflow from traces.
|
|
|
|

|
|
|
|
_Visualize your email operations with detailed span information including recipients, subjects, scheduling, and webhook processing._
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
npm install @kubiks/otel-inbound
|
|
# or
|
|
pnpm add @kubiks/otel-inbound
|
|
```
|
|
|
|
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `@inboundemail/sdk` >= 4.0.0
|
|
|
|
## Quick Start
|
|
|
|
```ts
|
|
import { Inbound } from "@inboundemail/sdk";
|
|
import { instrumentInbound } from "@kubiks/otel-inbound";
|
|
|
|
const inbound = instrumentInbound(new Inbound(process.env.INBOUND_API_KEY!));
|
|
|
|
await inbound.emails.send({
|
|
from: "hello@example.com",
|
|
to: ["user@example.com"],
|
|
subject: "Welcome",
|
|
html: "<p>Hello world</p>",
|
|
});
|
|
```
|
|
|
|
`instrumentInbound` wraps the instance you already use — no configuration changes
|
|
needed. Every SDK call creates a client span with useful attributes.
|
|
|
|
## What Gets Traced
|
|
|
|
This instrumentation wraps all Inbound API operations, creating spans for each:
|
|
|
|
### Email Operations
|
|
- `emails.send()` - Send email
|
|
- `emails.schedule()` - Schedule email for later delivery
|
|
- `emails.reply()` - Reply to an existing email thread
|
|
- `emails.retrieve()` - Retrieve email details
|
|
- `emails.listScheduled()` - List scheduled emails
|
|
- `emails.getScheduled()` - Get specific scheduled email
|
|
- `emails.cancelScheduled()` - Cancel a scheduled email
|
|
|
|
### Management Operations
|
|
- **Endpoints**: `list()`, `create()`, `get()`, `update()`, `delete()`
|
|
- **Addresses**: `list()`, `create()`, `get()`, `update()`, `delete()`
|
|
- **Domains**: `list()`, `create()`, `get()`, `update()`, `delete()`, `getDNS()`
|
|
|
|
### Thread & Attachment Operations
|
|
- **Threads**: `list()`, `get()`, `actions()`, `statistics()`
|
|
- **Attachments**: `download()`
|
|
|
|
### Webhook Receivers
|
|
- Incoming email webhooks (via `instrumentInboundWebhook`)
|
|
|
|
## Span Attributes
|
|
|
|
Each span includes relevant attributes based on the operation type:
|
|
|
|
### Base Attributes (All Operations)
|
|
|
|
| Attribute | Description | Example |
|
|
| ----------------------- | ---------------------------- | ------------------------ |
|
|
| `messaging.system` | Constant value `inbound` | `inbound` |
|
|
| `messaging.operation` | Operation type | `send`, `schedule`, etc. |
|
|
| `inbound.resource` | Resource being accessed | `emails`, `endpoints` |
|
|
| `inbound.target` | Full operation target | `emails.send` |
|
|
|
|
### Email Attributes
|
|
|
|
| Attribute | Description | Example |
|
|
| --------------------------- | --------------------------------- | --------------------------------------- |
|
|
| `inbound.message_id` | Message ID returned by Inbound | `msg_123` |
|
|
| `inbound.to_addresses` | Comma-separated TO addresses | `user@example.com, another@example.com` |
|
|
| `inbound.cc_addresses` | Comma-separated CC addresses | `cc@example.com` |
|
|
| `inbound.bcc_addresses` | Comma-separated BCC addresses | `bcc@example.com` |
|
|
| `inbound.recipient_count` | Total number of recipients | `3` |
|
|
| `inbound.from` | Sender email address | `noreply@example.com` |
|
|
| `inbound.subject` | Email subject line | `Welcome to our service` |
|
|
| `inbound.html_content` | HTML content (if capture enabled) | `<p>Hello</p>` |
|
|
| `inbound.text_content` | Text content (if capture enabled) | `Hello` |
|
|
|
|
### Scheduling Attributes
|
|
|
|
| Attribute | Description | Example |
|
|
| ----------------------- | ---------------------------- | ------------------------ |
|
|
| `inbound.scheduled_at` | Scheduled delivery time | `2025-01-01T00:00:00Z` |
|
|
| `inbound.schedule_id` | Schedule ID from API | `sched_123` |
|
|
|
|
### Management Attributes
|
|
|
|
| Attribute | Description | Example |
|
|
| ---------------------- | ------------------------- | ------------ |
|
|
| `inbound.endpoint_id` | Endpoint identifier | `ep_123` |
|
|
| `inbound.domain_id` | Domain identifier | `dom_123` |
|
|
| `inbound.address_id` | Email address identifier | `addr_123` |
|
|
|
|
### Thread & Attachment Attributes
|
|
|
|
| Attribute | Description | Example |
|
|
| ------------------------ | -------------------------- | -------------- |
|
|
| `inbound.thread_id` | Email thread identifier | `thread_123` |
|
|
| `inbound.attachment_id` | Attachment identifier | `attach_123` |
|
|
|
|
### Webhook Attributes
|
|
|
|
| Attribute | Description | Example |
|
|
| ---------------------- | ------------------------------ | ---------------- |
|
|
| `inbound.webhook_id` | Webhook identifier from header | `webhook_123` |
|
|
| `http.status_code` | HTTP response status code | `200` |
|
|
|
|
## Advanced Usage
|
|
|
|
### Webhook Receiver Instrumentation
|
|
|
|
Instrument Next.js route handlers that receive incoming emails:
|
|
|
|
```ts
|
|
import { instrumentInboundWebhook } from "@kubiks/otel-inbound";
|
|
|
|
export const POST = instrumentInboundWebhook(async (request: Request) => {
|
|
const email = await request.json();
|
|
|
|
// Process incoming email
|
|
console.log('Received email from:', email.from);
|
|
console.log('Subject:', email.subject);
|
|
|
|
// Your email processing logic here
|
|
await processIncomingEmail(email);
|
|
|
|
return Response.json({ success: true });
|
|
});
|
|
```
|
|
|
|
This creates SERVER spans (SpanKind.SERVER) that automatically capture:
|
|
- Email metadata from webhook payload
|
|
- Webhook headers
|
|
- Response status
|
|
- Any errors during processing
|
|
|
|
### Configuration Options
|
|
|
|
Control what data is captured in your spans:
|
|
|
|
```ts
|
|
import { instrumentInbound, type InstrumentInboundConfig } from "@kubiks/otel-inbound";
|
|
|
|
const config: InstrumentInboundConfig = {
|
|
// Capture email HTML/text content in spans (default: false)
|
|
captureEmailContent: true,
|
|
|
|
// Maximum content length before truncation (default: 1024)
|
|
maxContentLength: 2048,
|
|
};
|
|
|
|
const inbound = instrumentInbound(
|
|
new Inbound(process.env.INBOUND_API_KEY!),
|
|
config
|
|
);
|
|
```
|
|
|
|
**Note:** Be cautious when enabling `captureEmailContent` as it may capture sensitive information in your traces.
|
|
|
|
### Scheduling Emails
|
|
|
|
```ts
|
|
await inbound.emails.schedule({
|
|
from: "noreply@example.com",
|
|
to: "user@example.com",
|
|
subject: "Scheduled Newsletter",
|
|
html: "<p>Weekly update</p>",
|
|
scheduledAt: "2025-01-01T09:00:00Z",
|
|
});
|
|
|
|
// List all scheduled emails
|
|
const scheduled = await inbound.emails.listScheduled();
|
|
|
|
// Cancel a scheduled email
|
|
await inbound.emails.cancelScheduled("sched_123");
|
|
```
|
|
|
|
### Reply to Emails
|
|
|
|
```ts
|
|
await inbound.emails.reply({
|
|
from: "support@example.com",
|
|
to: "customer@example.com",
|
|
subject: "Re: Support Request",
|
|
html: "<p>Thanks for reaching out!</p>",
|
|
threadId: "thread_123", // Thread ID from webhook payload
|
|
});
|
|
```
|
|
|
|
### Domain Management
|
|
|
|
```ts
|
|
// Create a domain
|
|
const domain = await inbound.domains.create({
|
|
domain: "yourdomain.com",
|
|
});
|
|
|
|
// Get DNS records for verification
|
|
const dns = await inbound.domains.getDNS(domain.data.id);
|
|
console.log("Add these DNS records:", dns.data.records);
|
|
|
|
// List all domains
|
|
const domains = await inbound.domains.list();
|
|
```
|
|
|
|
### Endpoint Management
|
|
|
|
```ts
|
|
// Create webhook endpoint
|
|
const endpoint = await inbound.endpoints.create({
|
|
url: "https://yourdomain.com/webhook",
|
|
events: ["email.received"],
|
|
});
|
|
|
|
// Update endpoint
|
|
await inbound.endpoints.update(endpoint.data.id, {
|
|
url: "https://yourdomain.com/new-webhook",
|
|
});
|
|
|
|
// Delete endpoint
|
|
await inbound.endpoints.delete(endpoint.data.id);
|
|
```
|
|
|
|
### Complete Example with Webhook
|
|
|
|
```ts
|
|
import { Inbound } from "@inboundemail/sdk";
|
|
import { instrumentInbound, instrumentInboundWebhook } from "@kubiks/otel-inbound";
|
|
|
|
// Instrument the Inbound client
|
|
const inbound = instrumentInbound(
|
|
new Inbound(process.env.INBOUND_API_KEY!),
|
|
{ captureEmailContent: true }
|
|
);
|
|
|
|
// Send an email
|
|
await inbound.emails.send({
|
|
from: "hello@yourdomain.com",
|
|
to: "user@example.com",
|
|
subject: "Welcome!",
|
|
html: "<p>Thanks for signing up!</p>",
|
|
});
|
|
|
|
// Webhook handler for receiving emails
|
|
export const POST = instrumentInboundWebhook(
|
|
async (request: Request) => {
|
|
const email = await request.json();
|
|
|
|
// Automatically reply to incoming emails
|
|
await inbound.emails.reply({
|
|
from: email.to,
|
|
to: email.from,
|
|
subject: `Re: ${email.subject}`,
|
|
html: "<p>Thanks for your email! We'll get back to you soon.</p>",
|
|
threadId: email.threadId,
|
|
});
|
|
|
|
return Response.json({ processed: true });
|
|
},
|
|
{ captureEmailContent: true }
|
|
);
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|
|
|