SDK Reference
@develemail/sdk exposes a DevelEmail client with one property per
resource. All methods are async and throw DevelEmailError on non-2xx
responses.
import { DevelEmail } from '@develemail/sdk';
const client = new DevelEmail({
apiKey: 'your-api-key',
baseUrl: 'https://develemail.com/api', // http://localhost:3001 for a self-hosted dev instance
maxRetries: 3, // optional, defaults to 3. Retries on 429 and 5xx.
});Emails
| Method | Signature |
|---|---|
send | send(input: SendEmailInput, options?: SendOptions): Promise<EmailResponse> |
batch | batch(emails: SendEmailInput[], options?: SendOptions): Promise<BatchEmailResponse> |
get | get(id: string): Promise<EmailResponse> |
await client.emails.send({
from: '[email protected]',
to: ['[email protected]', '[email protected]'],
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
tags: ['receipts'],
});
const { id, status } = await client.emails.get('email_123');from accepts a bare address ([email protected]) or a display-name
mailbox (Acme <[email protected]>). Quote names that contain commas or
other specials ("Acme, Inc." <[email protected]>); non-ASCII names are
encoded for you. The address part must belong to a verified domain.
Email status & webhooks
status moves through queued → rendering → submitted_to_postfix →
sent, then to one of delivered, bounced, deferred (retrying),
rejected, or failed. delivered means the recipient's mail gateway
accepted the message with a 2xx response — it does not mean the message
reached the inbox. A delivered message can still be filtered to spam or
quarantined afterward. The full set of statuses is exported as
EMAIL_STATUSES / EmailStatus. Five more statuses cover paths outside
that flow:
suppressed— the recipient is on your suppression list, so nothing was sent.cancelled— the message was cancelled before it was handed off.scheduled— the message has a futuresend_atand is waiting.quarantined— the message was held back by content scanning and not sent.sandboxed— the message went to a sandbox address and was never delivered.
Every event type recorded for a project's emails — including delivered,
deferred, and bounced — is sent to that project's configured webhook URL
automatically; there's no per-event-type subscription to manage. The known
event types are exported as EMAIL_EVENT_TYPES / EmailEventType, and the
delivered/deferred/bounced event's metadata matches
DeliveryEventData (queueId, relay, response, timestamp). See
docs/webhooks.md in the repo for the full payload shape.
import { EMAIL_EVENT_TYPES, type DeliveryEventData } from '@develemail/sdk';
function handleWebhook(event: string, metadata: Record<string, unknown>) {
if (!EMAIL_EVENT_TYPES.includes(event as never)) {
console.warn(`Unrecognized event type: ${event}`);
return;
}
if (event === 'delivered') {
const { relay, response } = metadata as unknown as DeliveryEventData;
console.log(`Delivered via ${relay}: ${response}`);
}
}Verifying webhook signatures
verifyWebhookSignature(payload, signature, secret) checks the HMAC-SHA256
signature of the raw request body. It returns Promise<boolean>, accepts the
signature with or without a sha256= prefix, and compares in constant time.
Verify the raw string before parsing the JSON.
import { verifyWebhookSignature } from '@develemail/sdk';
const ok = await verifyWebhookSignature(rawBody, signatureHeader, secret);
if (!ok) return new Response('invalid signature', { status: 401 });Sandbox testing
Send to any address at @sandbox.develemail.com to exercise your integration
without delivering mail. The API accepts the send, stores it with status
sandboxed, and skips delivery. Sandbox sends are not metered: they bypass the
monthly quota and the daily tier limit and don't count toward usage.
const { status } = await client.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: 'Test',
text: 'Hello',
});
// status === 'sandboxed'A single send can't mix sandbox and real recipients. It fails with a 400 and
error code mixed_sandbox_recipients; send them as separate messages.
Domains
| Method | Signature |
|---|---|
list | list(): Promise<Domain[]> |
get | get(id: string): Promise<Domain> |
create | create(input: DomainCreateInput): Promise<Domain> |
delete | delete(id: string): Promise<void> |
verify | verify(id: string): Promise<Domain> |
const domain = await client.domains.create({ name: 'yourdomain.com' });
await client.domains.verify(domain.id);Templates
| Method | Signature |
|---|---|
list | list(): Promise<Template[]> |
get | get(id: string): Promise<Template> |
create | create(input: TemplateCreateInput): Promise<Template> |
update | update(id: string, input: TemplateUpdateInput): Promise<Template> |
delete | delete(id: string): Promise<void> |
const template = await client.templates.create({
name: 'welcome-email',
subject: 'Welcome, {{firstName}}!',
html: '<p>Hi {{firstName}}, welcome aboard.</p>',
variables: ['firstName'],
});Reference a template by name from emails.send via the template and
variables fields on SendEmailInput.
SMS, campaigns, contacts and lists
These resources have their own pages: SMS and Campaigns, contacts and lists.
Error handling
Every method throws DevelEmailError on a failed request, with the HTTP
status, a message, and an optional machine-readable code.
import { DevelEmail, DevelEmailError } from '@develemail/sdk';
try {
await client.emails.send(input);
} catch (err) {
if (err instanceof DevelEmailError) {
console.error(err.status, err.code, err.message);
}
}