bul-email 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,8 @@ const { id } = await bul.emails.send(
41
41
  (`maxRetries`, ברירת מחדל 3). לכל שליחה `Idempotency-Key` אחד, שחוזר בכל ניסיון — ניסיון חוזר אף פעם לא שולח פעמיים.
42
42
  - **שגיאות ברורות:** כל שגיאה מה-API היא `BulError` (או תת-מחלקה) עם `code` ו-`status`. ‏409 `route_paused` → ‏`RoutePausedError`
43
43
  (`reasonCode`, ‏`returns[]`) — שום דבר לא התקבל; שולחים בדרך אחרת ומדווחים עם `recipients.proof`.
44
+ - **חסימות:** ‏`suppressions.add` / ‏`import` יוצרות רק חסימה ידנית (`reason: "manual"`, או בלי). החזרות, תלונות והסרות — בול מוסיפה
45
+ לבד, ואי אפשר למחוק אותן (`remove` מחזיר 409 `suppression_not_removable`).
44
46
  - **Webhooks:** ‏`verifyWebhookSignature` / ‏`constructWebhookEvent` — ‏HMAC-SHA256, סבילות של 5 דקות, השוואה בזמן קבוע.
45
47
  ‏`BulEvent` מוקלד לפי `event`.
46
48
 
@@ -89,6 +91,8 @@ const { id } = await bul.emails.send(
89
91
  backoff (`maxRetries`, default 3). Each send gets one `Idempotency-Key`, reused on every retry — a retry never sends twice.
90
92
  - **Clear errors**: every API error is a `BulError` (or a subclass) with `code` and `status`. 409 `route_paused` → `RoutePausedError` (`reasonCode`,
91
93
  `returns[]`) — nothing was accepted; send another way and report it with `recipients.proof`.
94
+ - **Suppressions**: `suppressions.add` / `import` create manual suppressions only (`reason: "manual"`, or omit it). Bounces, complaints and
95
+ unsubscribes are added by Bul itself and cannot be removed (`remove` → 409 `suppression_not_removable`).
92
96
  - **Webhooks**: `verifyWebhookSignature` / `constructWebhookEvent` — HMAC-SHA256, 5-minute tolerance, constant-time comparison.
93
97
  `BulEvent` is typed by `event`.
94
98
 
@@ -98,6 +102,8 @@ Almost the same code: change the import and the key. Full guide: [Migrating from
98
102
 
99
103
  ### Changelog
100
104
 
105
+ - **0.1.2** — `suppressions.add` / `suppressions.import`: `reason` accepts only `"manual"` (new type `CreatableSuppressionReason`), matching the
106
+ API — creating any other reason now fails with 422 `reason_not_allowed`. `SuppressionReason` (all four) is unchanged for reading.
101
107
  - **0.1.1** — documentation (this changelog) and the version string. No behavior changes. The first release published straight from the build pipeline, with a verified publisher.
102
108
  - **0.1.0** — first release.
103
109
 
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ interface RequestSpec {
36
36
  /** POST that is safe to repeat even without an Idempotency-Key (the API deduplicates). */
37
37
  idempotent?: boolean;
38
38
  }
39
- export declare const SDK_VERSION = "0.1.1";
39
+ export declare const SDK_VERSION = "0.1.2";
40
40
  export declare class Bul {
41
41
  private readonly apiKey;
42
42
  private readonly baseUrl;
@@ -124,22 +124,24 @@ export declare class Bul {
124
124
  limit: number;
125
125
  offset: number;
126
126
  }>;
127
+ /** POST /v1/suppressions — always a manual suppression (`reason` may be omitted or "manual"). */
127
128
  add: (input: {
128
129
  recipient: string;
129
- reason?: T.SuppressionReason;
130
+ reason?: T.CreatableSuppressionReason;
130
131
  }) => Promise<T.Suppression | {
131
132
  recipient: string;
132
133
  already_suppressed: true;
133
134
  }>;
135
+ /** POST /v1/suppressions/import — always manual, like `add`. */
134
136
  import: (input: {
135
137
  recipients: string[];
136
- reason?: T.SuppressionReason;
138
+ reason?: T.CreatableSuppressionReason;
137
139
  }) => Promise<{
138
140
  added: number;
139
141
  already_suppressed: number;
140
142
  invalid: string[];
141
143
  }>;
142
- /** DELETE /v1/suppressions/{id or email} (full key). */
144
+ /** DELETE /v1/suppressions/{id or email} (full key). Only manual suppressions can be removed — others → 409 `suppression_not_removable`. */
143
145
  remove: (idOrEmail: string) => Promise<void>;
144
146
  };
145
147
  readonly recipients: {
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ export * from "./errors";
3
3
  export * from "./types";
4
4
  export { constructWebhookEvent, verifyWebhookSignature } from "./webhooks";
5
5
  const RETRY_STATUS = new Set([500, 502, 503, 504]);
6
- export const SDK_VERSION = "0.1.1";
6
+ export const SDK_VERSION = "0.1.2";
7
7
  function newKey() {
8
8
  return typeof crypto !== "undefined" && "randomUUID" in crypto
9
9
  ? crypto.randomUUID()
@@ -158,9 +158,11 @@ export class Bul {
158
158
  };
159
159
  suppressions = {
160
160
  list: (params = {}) => this.request({ method: "GET", path: "/v1/suppressions", query: params }),
161
+ /** POST /v1/suppressions — always a manual suppression (`reason` may be omitted or "manual"). */
161
162
  add: (input) => this.request({ method: "POST", path: "/v1/suppressions", body: input, idempotent: true }),
163
+ /** POST /v1/suppressions/import — always manual, like `add`. */
162
164
  import: (input) => this.request({ method: "POST", path: "/v1/suppressions/import", body: input, idempotent: true }),
163
- /** DELETE /v1/suppressions/{id or email} (full key). */
165
+ /** DELETE /v1/suppressions/{id or email} (full key). Only manual suppressions can be removed — others → 409 `suppression_not_removable`. */
164
166
  remove: (idOrEmail) => this.request({ method: "DELETE", path: `/v1/suppressions/${encodeURIComponent(idOrEmail)}` }),
165
167
  };
166
168
  recipients = {
package/dist/types.d.ts CHANGED
@@ -158,6 +158,11 @@ export interface WebhookTestResult {
158
158
  error: string | null;
159
159
  }
160
160
  export type SuppressionReason = "hard_bounce" | "complaint" | "manual" | "unsubscribe";
161
+ /**
162
+ * The only reason a customer can create (0.1.2). Bounces, complaints and unsubscribes are added by Bul itself, from the provider or the
163
+ * recipient, and cannot be removed; the API rejects any other reason with 422 `reason_not_allowed`.
164
+ */
165
+ export type CreatableSuppressionReason = "manual";
161
166
  export interface Suppression {
162
167
  id: string;
163
168
  recipient: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bul-email",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Official JavaScript/TypeScript client for the Bul email API (bul.friman.app)",
5
5
  "keywords": [
6
6
  "email",