Skip to content

Latest commit

 

History

History
242 lines (197 loc) · 7.72 KB

CONTRIBUTING.md

File metadata and controls

242 lines (197 loc) · 7.72 KB

Contributing to the Reloop Node.js SDK

Official npm package: reloop-email. This SDK is the reference implementation for the Reloop API — implement new endpoints here first, then port to other languages.

License: Apache License 2.0 with additional use restrictions from Reloop Labs.

API reference: reloop.sh/docs


Development setup

git clone git@github.com:reloop-labs/reloop-node.git
cd reloop-node
bun install
bun test
bun run build

Requires Bun (tests) and Node.js 20+ for the published package target (CI may use Node 22 for build tooling).


Project layout

src/
  index.ts               # Public entry: Reloop + selected types
  client.ts              # HTTP transport (internal)
  core/                  # Result types, client options
  services/
    api-key/
      create/
        create.ts        # validate + HTTP
        create.test.ts   # wire + validation
      list/
        list.ts
        list.test.ts
      ...
      api-key.ts         # thin facade
      api-key.test.ts
      test-helpers.ts
    mail/
      send/
        send.ts
        send.test.ts
      render/
        render.ts        # react -> html (optional peer dep)
        render.test.ts
      mail.ts
      mail.test.ts
      paths.ts
      result.ts
      errors.ts
      fields.ts
      test-helpers.ts
      types.ts
    contacts/
      create/ … delete/
      contacts.ts
      contacts.test.ts
      paths.ts
      result.ts
      errors.ts
      fields.ts
      test-helpers.ts
      types.ts
      property/
      group/
      channel/
    domain/
      create/ … verify/
      domain.ts
      domain.test.ts
      paths.ts
      result.ts
      errors.ts
      fields.ts
      test-helpers.ts
      types.ts
    webhook/
      create/ … retry-delivery/
      verify/
      webhook.ts
      webhook.test.ts
      paths.ts
      result.ts
      errors.ts
      fields.ts
      test-helpers.ts
      types.ts
    inbox/
      inbox.ts
      mailbox/ message/ thread/
tests/                   # cross-cutting package tests
  init.test.mjs
  exports.test.mjs
dist/

Conventions

Topic Rule
Init new Reloop({ apiKey, baseUrl? }) — apiKey required; no key / url aliases
HTTP errors (transport) Internal ReloopResult is { response, error }
Mail results Named fields: { response, emailError }
Contact results Named fields: { contact, contactError } (list: { contacts, contactError })
Webhook results Named fields: { webhook, webhookError } (list: { webhooks, webhookError }, deliveries: { deliveries, webhookError })
Inbox results Mailboxes: { mailbox, mailboxError } / { mailboxes, mailboxError }. Messages: { message, messageError } / { messages, messageError }. Threads: { thread, threadError } / { threads, threadError }
API key results Named fields: { apiKey, apiKeyError } (list: { apiKeys, apiKeyError })
Input validation Throw ReloopValidationError before fetch (no network). Rules match backend (name 1–255, page ≥ 1, limit 1–100, non-empty ids)
Api-key layout One folder per op: create/create.ts + create/create.test.ts. Validation in the op file; tests cover wire + validation. Shared fields.ts / paths.ts; thin api-key.ts facade
Mail & domain requests snake_case JSON (reply_to, click_tracking)
Mail react Rendered locally in mail/render/render.ts: optional peer @react-email/render first, else react-dom/server fallback; react is stripped from the wire body and replaces html
Contacts, API keys & inbox camelCase in JSON bodies as the API expects
API key methods 1:1 with backend: create, list, get, update, delete, rotate, enable, disable (no pause)
Types Request/response interfaces in each service’s types.ts
Public exports Reloop (+ default), options, Result/error types, api-key types, other resource modules as today. Do not export constructable ReloopClient or ApiKeyService
Tests Prefer foo.ts + foo.test.ts next to source. Mock fetch; assert path, method, headers, body, success + error Result
Imports Absolute only: @/... (never ../). Maps to src/* via tsconfig + Bun + tsup
Test runner Bun — bun test (see package.json scripts)
README Prerequisites, strict init, send example, api-key table, link to docs

Example: paired create test

// src/services/api-key/create/create.test.ts  (next to create.ts)
import assert from "node:assert/strict";
import { afterEach, mock, test } from "node:test";
import {
  apiKeyWithKeyFixture,
  assertAuthAndJson,
  createClient,
  getCall,
  jsonResponse,
  mockFetch,
  parseBody,
} from "@/services/api-key/test-helpers";

afterEach(() => mock.restoreAll());

test("create: POST /api/api-key/v1/ with name body", async () => {
  const payload = apiKeyWithKeyFixture();
  const fetchMock = mockFetch(jsonResponse(payload, 201));

  const { apiKey, apiKeyError } = await createClient().apiKey.create({ name: "Production Key" });

  assert.equal(apiKeyError, null);
  assert.deepEqual(apiKey, payload);
  const call = getCall(fetchMock);
  assert.equal(call.url, "https://reloop.sh/api/api-key/v1/");
  assert.equal(call.method, "POST");
  assertAuthAndJson(call.headers);
  assert.deepEqual(parseBody(call.body), { name: "Production Key" });
});

Run tests: bun test


Webhooks

  • API: reloop.webhook — create, list, get, update, delete, pause/enable/disable, trigger, listDeliveries, retryDelivery
  • Results: { webhook, webhookError } (and list/delivery variants)
  • Verify (local): WebhookService.constructEvent(rawBody, signatureHeader, secret) or reloop.webhook.verify({ payload, headers, secret }) — HMAC-SHA256 via X-Webhook-Signature (t=...,v1=...)
  • Tests: co-located under src/services/webhook/*/.test.ts and verify/verify.test.ts
  • Retry route: POST /api/webhook/deliveries/:id/retry (not under /v1)

Pull request checklist

  • npm test passes
  • npm run build succeeds
  • New routes have colocated *.test.ts (or tests under tests/)
  • Types match the OpenAPI spec at reloop.sh/docs
  • No API keys or secrets committed
  • Public export surface unchanged unless intentional (see Conventions)
  • Bump package.json version only when cutting a release (maintainers)

Releasing

Version lives in package.json and package-lock.json.

1. Bump version (SemVer)

# Edit package.json → "version": "2.0.1"
npm install   # refreshes package-lock.json
git add package.json package-lock.json
git commit -m "chore: release v2.0.1"
git push origin main

2. Tag and push

Tag must match the manifest: v + version.

git tag v2.0.1
git push origin v2.0.1

3. GitHub Release (automatic)

Pushing v* runs .github/workflows/release.yml, which:

  1. Verifies the tag matches package.json
  2. Runs npm test and npm run build
  3. Runs npm pack
  4. Creates a GitHub Release with:
    • reloop-node-{version}-source.zip
    • reloop-email-{version}.tgz

You can also run the workflow manually: Actions → Release → Run workflow.

4. Publish to npm (optional)

Registry publish uses .github/workflows/sdk-node-publish.yml or:

npm publish --provenance

Requires NPM_TOKEN (or OIDC) in repository secrets.


Troubleshooting

Issue Fix
Tag ≠ package.json version Align both, then recreate the tag
npm ci fails in CI Commit an updated package-lock.json
Release already exists Bump patch version (e.g. 2.0.1) and tag again