add changeset, update README

This commit is contained in:
Alex Holovach
2025-10-05 12:10:12 -05:00
parent c13d744313
commit f39a2772ca
4 changed files with 27 additions and 40 deletions

View File

@@ -0,0 +1,5 @@
---
"@kubiks/otel-resend": minor
---
Resend OpenTelemetry initial release

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

View File

@@ -4,6 +4,10 @@ OpenTelemetry instrumentation for the [Resend](https://resend.com) Node.js SDK.
Capture spans for every Resend API call, enrich them with operation metadata,
and keep an eye on message delivery from your traces.
![Resend Trace Visualization](https://github.com/kubiks-inc/otel/blob/main/images/otel-resend-trace.png)
_Visualize your email operations with detailed span information including recipients, subject lines, and delivery status._
## Installation
```bash
@@ -30,7 +34,7 @@ await resend.emails.send({
});
```
`instrumentResend` wraps the instance you already useno configuration changes
`instrumentResend` wraps the instance you already useno configuration changes
needed. Every SDK call creates a client span with useful attributes.
## What Gets Traced
@@ -41,36 +45,24 @@ This instrumentation specifically wraps the `resend.emails.send` method (and its
Each span includes:
| Attribute | Description | Example |
| --- | --- | --- |
| `messaging.system` | Constant value `resend` | `resend` |
| `messaging.operation` | Operation type | `send` |
| `resend.resource` | Resource name | `emails` |
| `resend.target` | Full operation target | `emails.send` |
| `resend.to_addresses` | Comma-separated TO addresses | `user@example.com, another@example.com` |
| `resend.cc_addresses` | Comma-separated CC addresses (if present) | `cc@example.com` |
| `resend.bcc_addresses` | Comma-separated BCC addresses (if present) | `bcc@example.com` |
| `resend.recipient_count` | Total number of recipients | `3` |
| `resend.from` | Sender email address | `noreply@example.com` |
| `resend.subject` | Email subject | `Welcome to our service` |
| `resend.template_id` | Template ID (if using templates) | `tmpl_123` |
| `resend.message_id` | Message ID returned by Resend | `email_123` |
| `resend.message_count` | Number of messages sent (always 1 for single sends) | `1` |
| Attribute | Description | Example |
| ------------------------ | --------------------------------------------------- | --------------------------------------- |
| `messaging.system` | Constant value `resend` | `resend` |
| `messaging.operation` | Operation type | `send` |
| `resend.resource` | Resource name | `emails` |
| `resend.target` | Full operation target | `emails.send` |
| `resend.to_addresses` | Comma-separated TO addresses | `user@example.com, another@example.com` |
| `resend.cc_addresses` | Comma-separated CC addresses (if present) | `cc@example.com` |
| `resend.bcc_addresses` | Comma-separated BCC addresses (if present) | `bcc@example.com` |
| `resend.recipient_count` | Total number of recipients | `3` |
| `resend.from` | Sender email address | `noreply@example.com` |
| `resend.subject` | Email subject | `Welcome to our service` |
| `resend.template_id` | Template ID (if using templates) | `tmpl_123` |
| `resend.message_id` | Message ID returned by Resend | `email_123` |
| `resend.message_count` | Number of messages sent (always 1 for single sends) | `1` |
The instrumentation captures email addresses and metadata to help with debugging and monitoring, while avoiding sensitive email content.
## Configuration
```ts
instrumentResend(resend, {
tracerName: "my-service",
tracer: myCustomTracer, // optional: bring your own tracer
});
```
- `tracerName`: Custom name for the tracer (defaults to `@kubiks/otel-resend`)
- `tracer`: Use an existing tracer instance instead of creating a new one
## License
MIT

View File

@@ -4,7 +4,6 @@ import {
SpanStatusCode,
trace,
type Span,
type Tracer,
} from "@opentelemetry/api";
import type { Resend, CreateEmailOptions, CreateEmailResponse } from "resend";
@@ -30,11 +29,6 @@ export const SEMATTRS_RESEND_BCC_ADDRESSES = "resend.bcc_addresses" as const;
export const SEMATTRS_RESEND_FROM = "resend.from" as const;
export const SEMATTRS_RESEND_SUBJECT = "resend.subject" as const;
export interface InstrumentResendConfig {
tracerName?: string;
tracer?: Tracer;
}
interface InstrumentedResend extends Resend {
[INSTRUMENTED_FLAG]?: true;
}
@@ -130,17 +124,13 @@ function finalizeSpan(span: Span, error?: unknown): void {
span.end();
}
export function instrumentResend(
client: Resend,
config?: InstrumentResendConfig,
): Resend {
export function instrumentResend(client: Resend): Resend {
// Check if already instrumented
if ((client as InstrumentedResend)[INSTRUMENTED_FLAG]) {
return client;
}
const tracerName = config?.tracerName ?? DEFAULT_TRACER_NAME;
const tracer = config?.tracer ?? trace.getTracer(tracerName);
const tracer = trace.getTracer(DEFAULT_TRACER_NAME);
const originalSend = client.emails.send.bind(client.emails);
const originalCreate = client.emails.create