Resend API - TundraSoft/tundra-connect GitHub Wiki
Client configuration and endpoint methods for @tundraconnect/resend.
import { Resend } from '@tundraconnect/resend';
const client = new Resend({
auth: { type: 'BEARER', token: 're_123456789' },
});| Option | Type | Required | Default | Description |
|---|---|---|---|---|
auth |
ResendAuth |
yes | — |
{ type: 'BEARER', token, prefix? }. prefix defaults to Bearer. |
headers |
Record<string, string> |
no | { 'User-Agent': … } |
Extra headers on every request. A User-Agent here replaces the default. |
baseURL |
string |
no | https://api.resend.com |
Override for a proxy or a test double. |
timeout |
number |
no | 30 |
Request timeout in seconds. |
maxRetryWait |
number |
no | — | Opt in to one RESTler retry on a 429 whose hint is within this many seconds. |
auth missing, not BEARER, or a blank token → CONFIG_INVALID_API_KEY
at construction.
Resend has two kinds of API key. A sending access key can only call
send and sendBatch; getEmail, rescheduleEmail and cancelEmail need
full access and otherwise fail with AUTH_FAILED
(vendorName: 'restricted_api_key').
Resend rejects a request with no User-Agent (HTTP 403). Server runtimes'
fetch doesn't reliably add one, so the client always sends
tundraconnect-resend (DEFAULT_USER_AGENT) unless you set your own.
The API key is never readable back off the client — there is no getter for it.
| Constant | Value | Meaning |
|---|---|---|
RESEND_API |
https://api.resend.com |
Default baseURL. |
MAX_BATCH_SIZE |
100 |
Most emails one sendBatch call accepts. |
DEFAULT_USER_AGENT |
tundraconnect-resend |
User-Agent sent when none is configured. |
DEFAULT_WEBHOOK_TOLERANCE_SECONDS |
300 |
Default verifyWebhook replay window, in seconds. |
POST /emails → EmailRefSchema ({ id })
const { id } = await client.send(
{
from: 'Acme <[email protected]>',
to: '[email protected]',
subject: 'Hello',
html: '<p>Hi</p>',
},
{ idempotencyKey: 'welcome-user_123' },
);| Field | Type | Required | Description |
|---|---|---|---|
from |
string |
yes |
email@domain or Name <email@domain>, on a verified domain. |
to |
string | string[] |
yes | 1–50 recipients. A bare string becomes a one-item array. |
subject |
string |
yes | Non-empty. |
html |
string |
no* | HTML body. |
text |
string |
no* | Plain-text body. Resend derives one from html when omitted. |
template |
TemplateSchema |
no* |
{ id, variables? } — a published template. |
cc |
string | string[] |
no | Carbon-copy recipient(s). |
bcc |
string | string[] |
no | Blind-carbon-copy recipient(s). |
reply_to |
string | string[] |
no | Reply-to address(es). |
headers |
Record<string, string> |
no | Custom email headers. |
attachments |
AttachmentSchema[] |
no | Base64 content or a URL path. |
tags |
TagSchema[] |
no |
{ name, value } pairs, echoed back by getEmail and in webhooks. |
scheduled_at |
string |
no | ISO 8601 or natural language (in 1 hour). |
topic_id |
string |
no | Topic governing contact subscription preferences. |
* One of html / text / template is required.
options.idempotencyKey (1–256 characters) is sent as Idempotency-Key.
Resend remembers it for 24 hours: a retry with the same key and body returns
the original result; the same key with a different body, or while the first
request is still in flight, fails with IDEMPOTENCY_CONFLICT.
POST /emails/batch → SendBatchResponseSchema ({ data: [{ id }] })
1–100 emails, each validated exactly like send. data[i] is the id of
emails[i]. The batch is all-or-nothing. attachments and scheduled_at
are rejected locally (Resend does not support them in a batch). A local
validation failure carries the offending email's index in its context.
GET /emails/{id} → EmailSchema
const email = await client.getEmail('4ef9a417-02e9-4d39-ad75-9611e0fcc33c');
console.log(email.last_event, email.tags);last_event is Resend's latest delivery event for the email (sent,
delivered, delivery_delayed, bounced, complained, opened,
clicked, scheduled, canceled, failed, suppressed, ...). It is typed
as an open string so a new event type never breaks retrieval.
PATCH /emails/{id} with { scheduled_at } → EmailRefSchema
Moves a scheduled, not-yet-sent email.
POST /emails/{id}/cancel → EmailRefSchema
Cancels a scheduled, not-yet-sent email.
Every id argument is percent-encoded into the path, and a blank id is
rejected locally as REQUEST_VALIDATION_ERROR.
Local check — no request, no API key used.
async function handle(req: Request): Promise<Response> {
const event = await client.verifyWebhook({
payload: await req.text(), // the raw body — never re-serialized JSON
headers: req.headers,
secret: 'whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw',
});
console.log(event.type, event.data);
return new Response(null, { status: 204 });
}| Field | Type | Required | Description |
|---|---|---|---|
payload |
string |
yes | The raw request body. |
headers |
WebhookHeadersLike |
yes | A Headers instance or a plain object; case-insensitive. |
secret |
string |
yes | The endpoint's signing secret, with or without whsec_. |
toleranceSeconds |
number |
no | Replay window, default 300, applied in both directions. |
nowMs |
number |
no | Clock override, for tests. |
Resend delivers webhooks through Svix: HMAC-SHA256 over
<svix-id>.<svix-timestamp>.<rawBody>, keyed by the base64-decoded secret,
carried in svix-signature as space-separated v1,<base64> entries (more
than one during secret rotation — any match is accepted, each compared in
constant time). The Standard Webhooks aliases webhook-id /
webhook-timestamp / webhook-signature are accepted too.
Resolves to the parsed, validated WebhookEventSchema
({ type, created_at, data }).