@illumetric/humblesend
v0.1.1
Published
Official server-side JavaScript and TypeScript SDK for Humblesend
Maintainers
Readme
@illumetric/humblesend
The official server-side JavaScript and TypeScript SDK for the Humblesend API.
It works in Node.js 18 and newer, Bun, Deno, and Cloudflare Workers. The package uses the runtime's
standard fetch implementation and has no runtime dependencies.
Never put a Humblesend API key in browser code. Calls from a browser expose the key to every visitor. Use this SDK only in a trusted server, worker, or backend application.
Install
npm install @illumetric/humblesendES modules:
import { Humblesend } from '@illumetric/humblesend';
const humblesend = new Humblesend({ apiKey: process.env.HUMBLESEND_API_KEY! });CommonJS:
const { Humblesend } = require('@illumetric/humblesend');
const humblesend = new Humblesend({ apiKey: process.env.HUMBLESEND_API_KEY });The default API URL is https://humblesend.com/api/v1. A custom baseUrl and fetch
implementation can be supplied for testing or another compatible runtime.
API keys
Create a workspace API key in Humblesend and keep it in a secret manager or environment variable. Grant only the scopes the integration needs:
transactional:sendfor transactional messagessubscribers:readto list or read contactssubscribers:writeto create or update contactslists:writewhen assigning List or Tag IDsconsent:writeto record marketing consent
Test keys exercise the transactional delivery workflow without contacting the email provider. Live keys can send real email and are required for contact and consent operations. Keep test and live keys in separate environment settings so a development deployment cannot send live email by mistake.
Transactional email
Send inline content:
const result = await humblesend.transactional.send(
{
to: { email: '[email protected]', name: 'Ada' },
subject: 'Your receipt',
text: 'Thanks for your order.'
},
{ idempotencyKey: `receipt:${order.id}` }
);
console.log(result.data.id, result.meta.requestId);For an internal contact-form notification, keep the authenticated Sender and direct replies to the customer:
await humblesend.transactional.send({
to: { email: '[email protected]', name: 'Support' },
replyTo: '[email protected]',
subject: 'New contact request',
text: 'A customer sent a contact request.'
});replyTo overrides the selected transactional Sender's configured reply-to address for only that
message. It must contain one email address.
Send a published Humblesend Email, schedule it, and pass typed template parameters:
await humblesend.transactional.send({
to: { externalCustomerId: customer.id, source: 'shop' },
emailId: '11111111-1111-4111-8111-111111111111',
parameters: { order: { number: order.number, total: order.total } },
sendAt: new Date('2026-10-01T09:00:00Z'),
tags: { kind: 'order_confirmation' }
});Inspect, reschedule, or cancel a message:
const message = await humblesend.transactional.get(messageId);
await humblesend.transactional.reschedule(messageId, { sendAt: new Date(Date.now() + 3_600_000) });
await humblesend.transactional.cancel(messageId);Date objects are serialized as ISO 8601 timestamps. ISO timestamp strings are accepted too.
Contacts
Create or update a contact by email:
const contact = await humblesend.contacts.upsert({
email: '[email protected]',
profile: { firstName: 'Ada', signupAt: new Date() },
externalIdentities: [{ namespace: 'shop.customer', value: 'customer-42' }],
listIds: ['11111111-1111-4111-8111-111111111111'],
tagIds: ['22222222-2222-4222-8222-222222222222']
});List and Tag memberships are assigned by ID on contact writes. Memberships are additive: omitting
IDs does not remove existing memberships. List and Tag discovery, removal, and management are not
part of SDK 0.1.0.
Other contact operations:
const page = await humblesend.contacts.list({ limit: 50, cursor });
const details = await humblesend.contacts.get(contactId);
await humblesend.contacts.update(contactId, { profile: { company: 'Analytical Engines' } });
await humblesend.contacts.upsertMany(contacts, { idempotencyKey: `import:${batchId}` });Single-contact upserts and updates do not use idempotency keys and are not automatically retried.
Marketing consent
Contact data and permission to send marketing email are intentionally separate. Recording a subscribed event requires a legal basis and either evidence or an explanatory note:
await humblesend.contacts.recordConsent(
contactId,
{
scope: 'global',
state: 'subscribed',
source: 'checkout',
legalBasis: 'consent',
evidence: 'Checked the marketing opt-in box during checkout',
effectiveAt: new Date()
},
{ idempotencyKey: `marketing-consent:${checkout.id}` }
);For a specific consent topic, use scope: 'topic' and provide topicId. For a global subscribed
event, topicIds can limit the grant to selected topics; omit it to include every active topic.
contacts.subscribe provides a safe convenience operation. It upserts the contact first and only
then records an explicit subscribed consent event:
const subscription = await humblesend.contacts.subscribe(
{
contact: { email: '[email protected]', listIds: [newsletterListId] },
consent: {
scope: 'global',
source: 'newsletter_form',
legalBasis: 'consent',
note: 'Submitted the footer newsletter form',
effectiveAt: new Date()
}
},
{ idempotencyKey: `newsletter:${formSubmissionId}` }
);
console.log(subscription.meta.contact.requestId, subscription.meta.consent.requestId);The two API calls are not atomic. If consent recording fails, the error includes
error.partial.contact; a contact may exist without consent, but this method never grants consent
before a successful contact upsert. Retry with the same caller-provided idempotency key.
Results and errors
Every direct API operation resolves to { data, meta }. Metadata contains the HTTP status, request
ID, idempotency key when used, and whether the server replayed a prior response.
import { HumblesendError } from '@illumetric/humblesend';
try {
await humblesend.transactional.send(message, { idempotencyKey: stableKey });
} catch (error) {
if (error instanceof HumblesendError) {
console.error(error.code, error.status, error.requestId, error.fields);
}
}The SDK automatically generates a UUID idempotency key when one is omitted. Generated keys protect retries during the current process. For deduplication across restarts, queues, or deployments, provide a stable key derived from the business operation and store it with that operation.
By default, requests time out after 30 seconds and retry at most twice. Network failures and HTTP
408, 429, 500, 502, 503, and 504 responses are retried only for reads and mutations that
carry an idempotency key. The same key is retained, and Retry-After is respected. Configure these
defaults when creating the client:
const humblesend = new Humblesend({
apiKey: process.env.HUMBLESEND_API_KEY!,
timeoutMs: 15_000,
maxRetries: 1
});Pass an AbortSignal in the final options argument to cancel any operation.
License
MIT
