@pilely/simple-email 0.4.3 → 0.5.0

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/dist/api.js CHANGED
@@ -1,7 +1,12 @@
1
- // The simple_email service's 13 POST routes, one function each. This is the
2
- // internal layer the hooks in hooks.ts call; the package exports the hooks,
3
- // not these. app_id moves between the body and the path from route to
4
- // route on this service.
1
+ // The simple_email service's page-side POST routes, one function each: the
2
+ // template send and the owner's account and template management. This is
3
+ // the layer the hooks in hooks.ts call; the package exports the hooks and
4
+ // `sendTemplate`, not the rest. app_id is in the path on every route but
5
+ // `accounts/create` (body) and `accounts/list` (none). Every function takes
6
+ // `{ challenge }` — a bot-check answer, or a function asked once per
7
+ // request — and every request it sends carries it. The freestyle send is
8
+ // not here: only an app's backend may make it, so it lives in send.ts,
9
+ // which needs no browser runtime.
5
10
  import { appId, call, collectPages, isMockMode, registerMockService } from "@pilely/core";
6
11
  import { createSimpleEmailFake } from "./mock.js";
7
12
  // Importing this package (its hooks import this module) is all an app does
@@ -25,65 +30,32 @@ function requireAppId() {
25
30
  }
26
31
  // ── Sending ──────────────────────────────────────────────────────────────
27
32
  /**
28
- * `app_id` is added from core's `appId()` — it is not a caller parameter.
29
- * Body size is capped on this route (like `createTemplate`/`updateTemplate`):
30
- * an oversize body is rejected by the server before any handler runs, so
31
- * the failure is not an `{ok:false}` envelope and the thrown `PilelyError`
32
- * carries a null `code`.
33
+ * The template send: `POST /accounts/{app_id}/templates/{template_id}/send`
34
+ * with the page's own app id. Sends that template, filled from `variables`,
35
+ * to the one recipient the owner stored on it — the caller can never point
36
+ * it anywhere else. `variables` is the body itself: each placeholder of the
37
+ * template's subject and html is read from the field of the same name, and
38
+ * every other field is ignored. A missing placeholder is a 400 and nothing
39
+ * is sent. The service admits the caller only when it is in the template's
40
+ * `send_group` and not in its `deny_group`; everyone else, an unknown
41
+ * template and an exhausted owner balance alike, gets the uniform 404.
33
42
  */
34
- export async function send(input) {
35
- const app_id = requireAppId();
36
- const idempotency_key = input.idempotency_key;
37
- const body = input.to_account !== undefined
38
- ? { app_id, to_account: input.to_account, template_id: input.template_id, variables: input.variables, idempotency_key }
39
- : "template_id" in input
40
- ? { app_id, to: input.to, template_id: input.template_id, variables: input.variables, idempotency_key }
41
- : { app_id, to: input.to, subject: input.subject, html: input.html, idempotency_key };
42
- return call({
43
- service: "simple-email",
44
- path: "/send",
45
- body,
46
- });
47
- }
48
- /** `app_id` lives in the body here, like `send` — not in the path. The
49
- * paged primitive; use `listAllSends` to walk to the end. */
50
- export async function listSends(options = {}) {
43
+ export async function sendTemplate(templateId, variables = {}, { challenge } = {}) {
51
44
  const json = await call({
52
45
  service: "simple-email",
53
- path: "/sends/list",
54
- body: {
55
- app_id: requireAppId(),
56
- limit: options.limit,
57
- after_created_time_stamp: options.after_created_time_stamp,
58
- after_send_id: options.after_send_id,
59
- },
60
- });
61
- return { sends: json.sends, next_cursor: json.next_cursor };
62
- }
63
- export async function listAllSends() {
64
- return collectPages(async (cursor) => {
65
- const page = await listSends({ limit: 100, ...(cursor ?? {}) });
66
- return { rows: page.sends, nextCursor: page.next_cursor };
46
+ path: `/accounts/${requireAppId()}/templates/${templateId}/send`,
47
+ body: variables,
48
+ challenge,
67
49
  });
50
+ return { send_id: json.send_id };
68
51
  }
69
52
  // ── Accounts ─────────────────────────────────────────────────────────────
70
- /** `sendGroup` is optional here — omit it (or pass `undefined`) and the
71
- * key is left off the wire, meaning owner-only. `setAccountAccess` below
72
- * is the opposite: its key is always required. `accountSenders` is
73
- * optional too, and left off the wire when `undefined` (the server then
74
- * uses `"send_group"`; it refuses an explicit `null` with a 400).
75
- * `replyTo` is optional as well: `undefined` leaves the key off the wire,
76
- * and omitted or `null` both mean no reply-to. */
77
- export async function createAccount(sendGroup, accountSenders, replyTo) {
78
- const body = {
79
- app_id: requireAppId(),
80
- };
81
- if (sendGroup !== undefined) {
82
- body.send_group = sendGroup;
83
- }
84
- if (accountSenders !== undefined) {
85
- body.account_senders = accountSenders;
86
- }
53
+ /** `replyTo` is optional: `undefined` leaves the key off the wire, and
54
+ * omitted or `null` both mean no reply-to. The account carries no send
55
+ * access of its own — who may send is each template's groups, and the
56
+ * app's backend. */
57
+ export async function createAccount(replyTo, { challenge } = {}) {
58
+ const body = { app_id: requireAppId() };
87
59
  if (replyTo !== undefined) {
88
60
  body.reply_to = replyTo;
89
61
  }
@@ -91,13 +63,14 @@ export async function createAccount(sendGroup, accountSenders, replyTo) {
91
63
  service: "simple-email",
92
64
  path: "/accounts/create",
93
65
  body,
66
+ challenge,
94
67
  });
95
68
  return json.account;
96
69
  }
97
70
  /** No `app_id` anywhere on this route — `after_app_id` is a cursor field,
98
71
  * not a selector. The paged primitive; use `listAllAccounts` to walk to
99
72
  * the end. */
100
- export async function listAccounts(options = {}) {
73
+ export async function listAccounts(options = {}, { challenge } = {}) {
101
74
  const json = await call({
102
75
  service: "simple-email",
103
76
  path: "/accounts/list",
@@ -106,58 +79,44 @@ export async function listAccounts(options = {}) {
106
79
  after_created_time_stamp: options.after_created_time_stamp,
107
80
  after_app_id: options.after_app_id,
108
81
  },
82
+ challenge,
109
83
  });
110
84
  return { accounts: json.accounts, next_cursor: json.next_cursor };
111
85
  }
112
- export async function listAllAccounts() {
86
+ export async function listAllAccounts(request = {}) {
113
87
  return collectPages(async (cursor) => {
114
- const page = await listAccounts({ limit: 100, ...(cursor ?? {}) });
88
+ const page = await listAccounts({ limit: 100, ...(cursor ?? {}) }, request);
115
89
  return { rows: page.accounts, nextCursor: page.next_cursor };
116
90
  });
117
91
  }
118
92
  /** Returns the account plus its live operational state — the four extra
119
93
  * fields are nullable siblings of `account`, not nested inside it. */
120
- export async function accountInfo() {
94
+ export async function accountInfo({ challenge } = {}) {
121
95
  const json = await call({
122
96
  service: "simple-email",
123
97
  path: `/accounts/${requireAppId()}/info`,
98
+ challenge,
124
99
  });
125
100
  return {
126
101
  account: json.account,
127
102
  daily_cap: json.daily_cap,
128
103
  sent_today: json.sent_today,
129
104
  credit_blocked: json.credit_blocked,
130
- phone_verified: json.phone_verified,
131
105
  };
132
106
  }
133
- export async function deleteAccount() {
134
- await call({ service: "simple-email", path: `/accounts/${requireAppId()}/delete` });
135
- }
136
- /** `sendGroup` is REQUIRED here and its key is always sent, even `null` —
137
- * the opposite of `createAccount`, where the key is optional. `null`
138
- * means owner-only. `accountSenders` is optional: `undefined` leaves the
139
- * key off the wire, which KEEPS the account's current value. */
140
- export async function setAccountAccess(sendGroup, accountSenders) {
141
- const body = { send_group: sendGroup };
142
- if (accountSenders !== undefined) {
143
- body.account_senders = accountSenders;
144
- }
145
- const json = await call({
146
- service: "simple-email",
147
- path: `/accounts/${requireAppId()}/access/set`,
148
- body,
149
- });
150
- return json.account;
107
+ export async function deleteAccount({ challenge } = {}) {
108
+ await call({ service: "simple-email", path: `/accounts/${requireAppId()}/delete`, challenge });
151
109
  }
152
110
  /** `replyTo` is REQUIRED and its key is always sent, even `null` — the
153
111
  * server refuses a missing key with a 400, so a typo can never silently
154
112
  * clear it. An address sets where recipients' replies go (every send from
155
113
  * the account carries it as its `Reply-To`); `null` clears it. */
156
- export async function setReplyTo(replyTo) {
114
+ export async function setReplyTo(replyTo, { challenge } = {}) {
157
115
  const json = await call({
158
116
  service: "simple-email",
159
117
  path: `/accounts/${requireAppId()}/set_reply_to`,
160
118
  body: { reply_to: replyTo },
119
+ challenge,
161
120
  });
162
121
  return json.account;
163
122
  }
@@ -165,20 +124,21 @@ export async function setReplyTo(replyTo) {
165
124
  /** Returns metadata only — no `html`. Use `templateInfo` to read the body
166
125
  * back.
167
126
  *
168
- * `name` is nullable because the service accepts a nameless template. It
169
- * stays a required POSITIONAL parameter — pass an explicit `null`, not
170
- * nothing — and `TemplateMeta.name` then comes back `null`. */
171
- export async function createTemplate(name, subject, html) {
127
+ * `name` is nullable because the service accepts a nameless template: pass
128
+ * an explicit `null`, and `TemplateMeta.name` comes back `null`. An
129
+ * omitted group is left off the wire (an empty group). */
130
+ export async function createTemplate(body, { challenge } = {}) {
172
131
  const json = await call({
173
132
  service: "simple-email",
174
133
  path: `/accounts/${requireAppId()}/templates/create`,
175
- body: { name, subject, html },
134
+ body,
135
+ challenge,
176
136
  });
177
137
  return json.template;
178
138
  }
179
139
  /** The paged primitive; use `listAllTemplates` to walk to the end. Rows
180
140
  * carry metadata only — no `html`. */
181
- export async function listTemplates(options = {}) {
141
+ export async function listTemplates(options = {}, { challenge } = {}) {
182
142
  const json = await call({
183
143
  service: "simple-email",
184
144
  path: `/accounts/${requireAppId()}/templates/list`,
@@ -187,39 +147,45 @@ export async function listTemplates(options = {}) {
187
147
  after_created_time_stamp: options.after_created_time_stamp,
188
148
  after_template_id: options.after_template_id,
189
149
  },
150
+ challenge,
190
151
  });
191
152
  return { templates: json.templates, next_cursor: json.next_cursor };
192
153
  }
193
- export async function listAllTemplates() {
154
+ export async function listAllTemplates(request = {}) {
194
155
  return collectPages(async (cursor) => {
195
- const page = await listTemplates({ limit: 100, ...(cursor ?? {}) });
156
+ const page = await listTemplates({ limit: 100, ...(cursor ?? {}) }, request);
196
157
  return { rows: page.templates, nextCursor: page.next_cursor };
197
158
  });
198
159
  }
199
160
  /** The only template route whose answer carries `html`. */
200
- export async function templateInfo(templateNanoid) {
161
+ export async function templateInfo(templateNanoid, { challenge } = {}) {
201
162
  const json = await call({
202
163
  service: "simple-email",
203
164
  path: `/accounts/${requireAppId()}/templates/${templateNanoid}/info`,
165
+ challenge,
204
166
  });
205
167
  return json.template;
206
168
  }
207
169
  /**
208
- * A FULL REPLACE of both `subject` and `html` — never a patch — and `name`
209
- * is not updatable at all. Returns metadata only, like `createTemplate`.
210
- * Subject to the same oversize-body caveat as `send`/`createTemplate`.
170
+ * A FULL REPLACE of both `subject` and `html`; `recipient`, `send_group` and
171
+ * `deny_group` change only when their key is sent. `name` is not updatable
172
+ * at all. Returns metadata only, like `createTemplate`. An oversize body is
173
+ * rejected by the server before any handler runs, so the thrown
174
+ * `PilelyError` carries a null `code`.
211
175
  */
212
- export async function updateTemplate(templateNanoid, body) {
176
+ export async function updateTemplate(templateNanoid, body, { challenge } = {}) {
213
177
  const json = await call({
214
178
  service: "simple-email",
215
179
  path: `/accounts/${requireAppId()}/templates/${templateNanoid}/update`,
216
180
  body,
181
+ challenge,
217
182
  });
218
183
  return json.template;
219
184
  }
220
- export async function deleteTemplate(templateNanoid) {
185
+ export async function deleteTemplate(templateNanoid, { challenge } = {}) {
221
186
  await call({
222
187
  service: "simple-email",
223
188
  path: `/accounts/${requireAppId()}/templates/${templateNanoid}/delete`,
189
+ challenge,
224
190
  });
225
191
  }
package/dist/hooks.d.ts CHANGED
@@ -1,65 +1,73 @@
1
+ import type { ChallengeOption } from "@pilely/core";
1
2
  import type { UseInfiniteQueryResult, UseMutationResult, UseQueryResult } from "@tanstack/react-query";
2
- import type { SendInput, SendResult } from "./api.js";
3
- import type { Account, AccountInfoEnvelope, AccountSenders, SendRow, Template, TemplateMeta } from "./types.js";
4
- export interface PageOptions {
3
+ import type { TemplateSendResult } from "./api.js";
4
+ import type { Account, AccountInfoEnvelope, Template, TemplateMeta } from "./types.js";
5
+ export interface PageOptions extends ChallengeOption {
5
6
  /** Rows per page; the server defaults to 50 and caps at 100. */
6
7
  limit?: number;
7
8
  }
9
+ /** `recipient` is required: the ONE address the template's sends go to.
10
+ * An omitted (or `null`) group is empty: an empty `send_group` admits
11
+ * nobody, an empty `deny_group` refuses nobody. */
8
12
  export interface CreateTemplateInput {
9
13
  /** `null` for a nameless template. */
10
14
  name: string | null;
11
15
  subject: string;
12
16
  html: string;
17
+ recipient: string;
18
+ /** An owner group, or the platform-wide anonymous or authenticated group. */
19
+ send_group?: string | null;
20
+ /** An owner group only. */
21
+ deny_group?: string | null;
13
22
  }
14
- /** `useCreateEmailAccount`'s object form. Every key is optional: an omitted
15
- * `send_group` means owner-only, an omitted `account_senders` means
16
- * `"send_group"`, and an omitted (or `null`) `reply_to` means no reply-to. */
23
+ /** `useCreateEmailAccount`'s object form. An omitted (or `null`) `reply_to`
24
+ * means no reply-to. */
17
25
  export interface CreateEmailAccountInput {
18
- send_group?: string | null;
19
- account_senders?: AccountSenders;
20
26
  /** The address recipients' replies go to. */
21
27
  reply_to?: string | null;
22
28
  }
23
- /** `useSetEmailAccountAccess`'s object form. `send_group` is required (`null`
24
- * = owner-only); an omitted `account_senders` keeps the current value. */
25
- export interface SetEmailAccountAccessInput {
26
- send_group: string | null;
27
- account_senders?: AccountSenders;
28
- }
29
- /** A full replace of both `subject` and `html`; `name` is not updatable. */
29
+ /** A full replace of both `subject` and `html`; `name` is not updatable. An
30
+ * omitted `recipient`, `send_group` or `deny_group` keeps the stored value;
31
+ * `null` empties a group. */
30
32
  export interface UpdateTemplateInput {
31
33
  templateId: string;
32
34
  subject: string;
33
35
  html: string;
36
+ recipient?: string;
37
+ send_group?: string | null;
38
+ deny_group?: string | null;
39
+ }
40
+ /** `useSendTemplate`'s variables: the template and its placeholder values. */
41
+ export interface SendTemplateInput {
42
+ templateId: string;
43
+ /** One string per placeholder of the template's subject and html; omitted
44
+ * is `{}`, for a template without placeholders. */
45
+ variables?: Record<string, string>;
34
46
  }
35
- /** Sends one email from this app's account: a raw send (`to`) or an account
36
- * send (`to_account: "self" | "owner"`). Not optimistic: a send counts only
37
- * once the server accepted it. A repeat carrying the same `idempotency_key`
38
- * answers the first send with `replayed: true`. */
39
- export declare function useSend(): UseMutationResult<SendResult, Error, SendInput>;
40
- /** This app's outbox, newest first, as an infinite query. */
41
- export declare function useSends(options?: PageOptions): UseInfiniteQueryResult<SendRow[], Error>;
47
+ /** The template send, from a page: the template, filled from `variables`,
48
+ * to the one recipient its owner stored on it. Not optimistic: a send
49
+ * counts only once the server accepted it. The answer is `{ send_id }`,
50
+ * never the address. It counts toward the app's daily send cap, so it
51
+ * refreshes `useEmailAccount()`. */
52
+ export declare function useSendTemplate(options?: ChallengeOption): UseMutationResult<TemplateSendResult, Error, SendTemplateInput>;
42
53
  /** The caller's email accounts across apps, as an infinite query. */
43
54
  export declare function useEmailAccounts(options?: PageOptions): UseInfiniteQueryResult<Account[], Error>;
44
- /** This app's account with its live state: daily cap, sent today, credit
45
- * and phone verification. */
46
- export declare function useEmailAccount(): UseQueryResult<AccountInfoEnvelope, Error>;
47
- /** Creates this app's account; the variable is its optional send group
48
- * (`null` or omitted: owner-only), or `{ send_group?, account_senders?, reply_to? }`. */
49
- export declare function useCreateEmailAccount(): UseMutationResult<Account, Error, string | null | void | CreateEmailAccountInput>;
55
+ /** This app's account with its live state: daily cap, sent today and
56
+ * credit. */
57
+ export declare function useEmailAccount(options?: ChallengeOption): UseQueryResult<AccountInfoEnvelope, Error>;
58
+ /** Creates this app's account; the variable is optional, or
59
+ * `{ reply_to? }`. */
60
+ export declare function useCreateEmailAccount(options?: ChallengeOption): UseMutationResult<Account, Error, void | CreateEmailAccountInput>;
50
61
  /** Deletes this app's account and the templates under it. */
51
- export declare function useDeleteEmailAccount(): UseMutationResult<void, Error, void>;
52
- /** Replaces this app's send group (`null` means owner-only); the object form
53
- * `{ send_group, account_senders? }` also sets who may make account sends. */
54
- export declare function useSetEmailAccountAccess(): UseMutationResult<Account, Error, string | null | SetEmailAccountAccessInput>;
62
+ export declare function useDeleteEmailAccount(options?: ChallengeOption): UseMutationResult<void, Error, void>;
55
63
  /** Sets this app's reply-to address — where recipients' replies go; every
56
64
  * send from the account carries it as its `Reply-To` — or clears it with
57
65
  * `null`. The variable is required: there is no "keep" form. */
58
- export declare function useSetEmailReplyTo(): UseMutationResult<Account, Error, string | null>;
66
+ export declare function useSetEmailReplyTo(options?: ChallengeOption): UseMutationResult<Account, Error, string | null>;
59
67
  /** This app's templates (metadata only), as an infinite query. */
60
68
  export declare function useTemplates(options?: PageOptions): UseInfiniteQueryResult<TemplateMeta[], Error>;
61
69
  /** One template with its `html`. An `undefined` id disables the query. */
62
- export declare function useTemplate(templateId: string | undefined): UseQueryResult<Template, Error>;
63
- export declare function useCreateTemplate(): UseMutationResult<TemplateMeta, Error, CreateTemplateInput>;
64
- export declare function useUpdateTemplate(): UseMutationResult<TemplateMeta, Error, UpdateTemplateInput>;
65
- export declare function useDeleteTemplate(): UseMutationResult<void, Error, string>;
70
+ export declare function useTemplate(templateId: string | undefined, options?: ChallengeOption): UseQueryResult<Template, Error>;
71
+ export declare function useCreateTemplate(options?: ChallengeOption): UseMutationResult<TemplateMeta, Error, CreateTemplateInput>;
72
+ export declare function useUpdateTemplate(options?: ChallengeOption): UseMutationResult<TemplateMeta, Error, UpdateTemplateInput>;
73
+ export declare function useDeleteTemplate(options?: ChallengeOption): UseMutationResult<void, Error, string>;
package/dist/hooks.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { serviceQueryKey, useServiceInfiniteQuery, useServiceMutation, useServiceQuery } from "@pilely/core";
2
- import { accountInfo, createAccount, createTemplate, deleteAccount, deleteTemplate, listAccounts, listSends, listTemplates, send, setAccountAccess, setReplyTo, templateInfo, updateTemplate, } from "./api.js";
3
- /** `["pilely", "simple-email", "sends", options]` */
4
- const sendsKey = () => serviceQueryKey("simple-email", "sends");
2
+ import { accountInfo, createAccount, createTemplate, deleteAccount, deleteTemplate, listAccounts, listTemplates, sendTemplate, setReplyTo, templateInfo, updateTemplate, } from "./api.js";
5
3
  /** `["pilely", "simple-email", "accounts", options]` */
6
4
  const accountsKey = () => serviceQueryKey("simple-email", "accounts");
7
5
  /** `["pilely", "simple-email", "account"]` — this app's account info. */
@@ -11,115 +9,105 @@ const templatesKey = () => serviceQueryKey("simple-email", "templates");
11
9
  /** `["pilely", "simple-email", "template", templateId]` */
12
10
  const templateKey = (templateId) => serviceQueryKey("simple-email", "template", templateId);
13
11
  // ── Sending ──────────────────────────────────────────────────────────────
14
- /** Sends one email from this app's account: a raw send (`to`) or an account
15
- * send (`to_account: "self" | "owner"`). Not optimistic: a send counts only
16
- * once the server accepted it. A repeat carrying the same `idempotency_key`
17
- * answers the first send with `replayed: true`. */
18
- export function useSend() {
19
- return useServiceMutation("useSend", {
20
- mutationFn: send,
21
- invalidates: () => [sendsKey()],
22
- });
23
- }
24
- /** This app's outbox, newest first, as an infinite query. */
25
- export function useSends(options = {}) {
26
- const { limit } = options;
27
- return useServiceInfiniteQuery("useSends", {
28
- queryKey: [...sendsKey(), { limit }],
29
- fetchPage: async (cursor) => {
30
- const page = await listSends({ limit, ...(cursor ?? {}) });
31
- return { rows: page.sends, nextCursor: page.next_cursor };
32
- },
12
+ /** The template send, from a page: the template, filled from `variables`,
13
+ * to the one recipient its owner stored on it. Not optimistic: a send
14
+ * counts only once the server accepted it. The answer is `{ send_id }`,
15
+ * never the address. It counts toward the app's daily send cap, so it
16
+ * refreshes `useEmailAccount()`. */
17
+ export function useSendTemplate(options = {}) {
18
+ const { challenge } = options;
19
+ return useServiceMutation("useSendTemplate", {
20
+ mutationFn: ({ templateId, variables }) => sendTemplate(templateId, variables, { challenge }),
21
+ invalidates: () => [accountKey()],
33
22
  });
34
23
  }
35
24
  // ── Accounts ─────────────────────────────────────────────────────────────
36
25
  /** The caller's email accounts across apps, as an infinite query. */
37
26
  export function useEmailAccounts(options = {}) {
38
- const { limit } = options;
27
+ const { limit, challenge } = options;
39
28
  return useServiceInfiniteQuery("useEmailAccounts", {
40
29
  queryKey: [...accountsKey(), { limit }],
41
30
  fetchPage: async (cursor) => {
42
- const page = await listAccounts({ limit, ...(cursor ?? {}) });
31
+ const page = await listAccounts({ limit, ...(cursor ?? {}) }, { challenge });
43
32
  return { rows: page.accounts, nextCursor: page.next_cursor };
44
33
  },
45
34
  });
46
35
  }
47
- /** This app's account with its live state: daily cap, sent today, credit
48
- * and phone verification. */
49
- export function useEmailAccount() {
50
- return useServiceQuery("useEmailAccount", { queryKey: accountKey(), queryFn: accountInfo });
36
+ /** This app's account with its live state: daily cap, sent today and
37
+ * credit. */
38
+ export function useEmailAccount(options = {}) {
39
+ const { challenge } = options;
40
+ return useServiceQuery("useEmailAccount", {
41
+ queryKey: accountKey(),
42
+ queryFn: () => accountInfo({ challenge }),
43
+ });
51
44
  }
52
- /** Creates this app's account; the variable is its optional send group
53
- * (`null` or omitted: owner-only), or `{ send_group?, account_senders?, reply_to? }`. */
54
- export function useCreateEmailAccount() {
45
+ /** Creates this app's account; the variable is optional, or
46
+ * `{ reply_to? }`. */
47
+ export function useCreateEmailAccount(options = {}) {
48
+ const { challenge } = options;
55
49
  return useServiceMutation("useCreateEmailAccount", {
56
- mutationFn: (input) => typeof input === "object" && input !== null
57
- ? createAccount(input.send_group, input.account_senders, input.reply_to)
58
- : createAccount(input ?? undefined),
50
+ mutationFn: (input) => createAccount(input ? input.reply_to : undefined, { challenge }),
59
51
  invalidates: () => [accountsKey(), accountKey()],
60
52
  });
61
53
  }
62
54
  /** Deletes this app's account and the templates under it. */
63
- export function useDeleteEmailAccount() {
55
+ export function useDeleteEmailAccount(options = {}) {
56
+ const { challenge } = options;
64
57
  return useServiceMutation("useDeleteEmailAccount", {
65
- mutationFn: () => deleteAccount(),
58
+ mutationFn: () => deleteAccount({ challenge }),
66
59
  invalidates: () => [accountsKey(), accountKey(), templatesKey(), serviceQueryKey("simple-email", "template")],
67
60
  });
68
61
  }
69
- /** Replaces this app's send group (`null` means owner-only); the object form
70
- * `{ send_group, account_senders? }` also sets who may make account sends. */
71
- export function useSetEmailAccountAccess() {
72
- return useServiceMutation("useSetEmailAccountAccess", {
73
- mutationFn: (input) => typeof input === "object" && input !== null
74
- ? setAccountAccess(input.send_group, input.account_senders)
75
- : setAccountAccess(input),
76
- invalidates: () => [accountsKey(), accountKey()],
77
- });
78
- }
79
62
  /** Sets this app's reply-to address — where recipients' replies go; every
80
63
  * send from the account carries it as its `Reply-To` — or clears it with
81
64
  * `null`. The variable is required: there is no "keep" form. */
82
- export function useSetEmailReplyTo() {
65
+ export function useSetEmailReplyTo(options = {}) {
66
+ const { challenge } = options;
83
67
  return useServiceMutation("useSetEmailReplyTo", {
84
- mutationFn: (replyTo) => setReplyTo(replyTo),
68
+ mutationFn: (replyTo) => setReplyTo(replyTo, { challenge }),
85
69
  invalidates: () => [accountsKey(), accountKey()],
86
70
  });
87
71
  }
88
72
  // ── Templates ────────────────────────────────────────────────────────────
89
73
  /** This app's templates (metadata only), as an infinite query. */
90
74
  export function useTemplates(options = {}) {
91
- const { limit } = options;
75
+ const { limit, challenge } = options;
92
76
  return useServiceInfiniteQuery("useTemplates", {
93
77
  queryKey: [...templatesKey(), { limit }],
94
78
  fetchPage: async (cursor) => {
95
- const page = await listTemplates({ limit, ...(cursor ?? {}) });
79
+ const page = await listTemplates({ limit, ...(cursor ?? {}) }, { challenge });
96
80
  return { rows: page.templates, nextCursor: page.next_cursor };
97
81
  },
98
82
  });
99
83
  }
100
84
  /** One template with its `html`. An `undefined` id disables the query. */
101
- export function useTemplate(templateId) {
85
+ export function useTemplate(templateId, options = {}) {
86
+ const { challenge } = options;
102
87
  return useServiceQuery("useTemplate", {
103
88
  queryKey: templateKey(templateId),
104
- queryFn: () => templateInfo(templateId),
89
+ queryFn: () => templateInfo(templateId, { challenge }),
105
90
  enabled: templateId !== undefined,
106
91
  });
107
92
  }
108
- export function useCreateTemplate() {
93
+ export function useCreateTemplate(options = {}) {
94
+ const { challenge } = options;
109
95
  return useServiceMutation("useCreateTemplate", {
110
- mutationFn: ({ name, subject, html }) => createTemplate(name, subject, html),
96
+ mutationFn: (input) => createTemplate(input, { challenge }),
111
97
  invalidates: () => [templatesKey()],
112
98
  });
113
99
  }
114
- export function useUpdateTemplate() {
100
+ export function useUpdateTemplate(options = {}) {
101
+ const { challenge } = options;
115
102
  return useServiceMutation("useUpdateTemplate", {
116
- mutationFn: ({ templateId, subject, html }) => updateTemplate(templateId, { subject, html }),
103
+ mutationFn: ({ templateId, ...body }) => updateTemplate(templateId, body, { challenge }),
117
104
  invalidates: (_template, { templateId }) => [templatesKey(), templateKey(templateId)],
118
105
  });
119
106
  }
120
- export function useDeleteTemplate() {
107
+ export function useDeleteTemplate(options = {}) {
108
+ const { challenge } = options;
121
109
  return useServiceMutation("useDeleteTemplate", {
122
- mutationFn: deleteTemplate,
110
+ mutationFn: (templateId) => deleteTemplate(templateId, { challenge }),
123
111
  invalidates: (_none, templateId) => [templatesKey(), templateKey(templateId)],
124
112
  });
125
113
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSend, useSends, useSetEmailAccountAccess, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
2
- export type { CreateEmailAccountInput, CreateTemplateInput, PageOptions, SetEmailAccountAccessInput, UpdateTemplateInput, } from "./hooks.js";
3
- export type { AccountSendInput, RawSendInput, SendContent, SendInput, SendKey, SendResult } from "./api.js";
4
- export type { Account, AccountSenders, SimpleEmailErrorCode, AccountCursor, AccountInfoEnvelope, SendCursor, SendRow, Template, TemplateCursor, TemplateMeta, ToAccount, } from "./types.js";
1
+ export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSendTemplate, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
2
+ export type { CreateEmailAccountInput, CreateTemplateInput, PageOptions, SendTemplateInput, UpdateTemplateInput, } from "./hooks.js";
3
+ export { sendTemplate } from "./api.js";
4
+ export type { TemplateSendResult } from "./api.js";
5
+ export { SIMPLE_EMAIL_ORIGIN, SendError, send } from "./send.js";
6
+ export type { SendContent, SendInput, SendKey, SendOptions, SendResult } from "./send.js";
7
+ export type { Account, SimpleEmailErrorCode, AccountCursor, AccountInfoEnvelope, Template, TemplateCursor, TemplateMeta, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // @pilely/simple-email — React hooks over the simple_email service: a query
2
2
  // hook for every read, a mutation hook for every write. Render them inside
3
- // @pilely/core's <PilelyProvider>.
4
- export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSend, useSends, useSetEmailAccountAccess, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
3
+ // @pilely/core's <PilelyProvider>. `sendTemplate` is the template send a
4
+ // page makes, as a plain function. `send` is the freestyle send for an
5
+ // app's backend only; a server imports it from `@pilely/simple-email/send`,
6
+ // which loads none of this entry's peers.
7
+ export { useCreateEmailAccount, useCreateTemplate, useDeleteEmailAccount, useDeleteTemplate, useEmailAccount, useEmailAccounts, useSendTemplate, useSetEmailReplyTo, useTemplate, useTemplates, useUpdateTemplate, } from "./hooks.js";
8
+ export { sendTemplate } from "./api.js";
9
+ export { SIMPLE_EMAIL_ORIGIN, SendError, send } from "./send.js";
package/dist/mock.d.ts CHANGED
@@ -1,2 +1,9 @@
1
1
  import type { MockServiceFactory } from "@pilely/core";
2
+ /** The fake's ids for the two platform-wide groups: fixed test values, not
3
+ * the production ids. `@pilely/simple-group`'s fake answers the same two
4
+ * from `groups/anonymous` and `groups/authenticated`, so in mock mode an id
5
+ * read from `anonymousGroupId()` / `authenticatedGroupId()` works as a
6
+ * template's `send_group` here. Not part of the package's public surface. */
7
+ export declare const MOCK_ANON_GROUP = "mockAnonymousGroup0001";
8
+ export declare const MOCK_AUTH_GROUP = "mockAuthenticatedGrp01";
2
9
  export declare const createSimpleEmailFake: MockServiceFactory;