better-auth-lead 0.4.3 → 0.6.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/README.md CHANGED
@@ -56,6 +56,8 @@ const authClient = createAuthClient({
56
56
 
57
57
  ### Subscribe
58
58
 
59
+ Provide an `email` to subscribe an anonymous lead:
60
+
59
61
  ```ts
60
62
  // POST /lead/subscribe
61
63
  const { data, error } = await authClient.lead.subscribe({
@@ -67,6 +69,19 @@ const { data, error } = await authClient.lead.subscribe({
67
69
  });
68
70
  ```
69
71
 
72
+ Or omit `email` to subscribe the currently authenticated user. The lead is associated to the session user's `id` (a valid session cookie is required):
73
+
74
+ ```ts
75
+ // POST /lead/subscribe
76
+ const { data, error } = await authClient.lead.subscribe({
77
+ metadata: {
78
+ preferences: 'engineering',
79
+ },
80
+ });
81
+ ```
82
+
83
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
84
+
70
85
  ### Verify
71
86
 
72
87
  ```ts
@@ -80,7 +95,7 @@ await authClient.lead.verify({
80
95
 
81
96
  ### Unsubscribe
82
97
 
83
- The unsubscribe endpoint is designed for [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058) one-click unsubscribe. The signed `token` is embedded in the `unsubscribeUrl` provided to `sendVerificationEmail` and should be used in `List-Unsubscribe` email headers — email clients (Gmail, Apple Mail, Yahoo Mail) will POST to this URL automatically when the user clicks "Unsubscribe".
98
+ The unsubscribe endpoint is designed for [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058) one-click unsubscribe. The signed `token` is embedded in the `unsubscribeUrl` provided to `sendConfirmationEmail` and should be used in `List-Unsubscribe` email headers — email clients (Gmail, Apple Mail, Yahoo Mail) will POST to this URL automatically when the user clicks "Unsubscribe".
84
99
 
85
100
  ```ts
86
101
  // POST /lead/unsubscribe?token=<signed-token>
@@ -89,8 +104,17 @@ const { data, error } = await authClient.lead.unsubscribe({
89
104
  });
90
105
  ```
91
106
 
107
+ For an authenticated user (e.g. from a "Manage preferences" page in your app), use the session-based endpoint. It requires a valid session and deletes the lead associated with the session user's `id`:
108
+
109
+ ```ts
110
+ // POST /lead/unsubscribe-session
111
+ const { data, error } = await authClient.lead.unsubscribeSession();
112
+ ```
113
+
92
114
  ### Resend
93
115
 
116
+ Resend the confirmation email by `email`:
117
+
94
118
  ```ts
95
119
  // POST /lead/resend
96
120
  const { data, error } = await authClient.lead.resend({
@@ -98,25 +122,132 @@ const { data, error } = await authClient.lead.resend({
98
122
  });
99
123
  ```
100
124
 
125
+ Or omit `email` to resend for the currently authenticated user (lead is looked up by the session user's `id`):
126
+
127
+ ```ts
128
+ // POST /lead/resend
129
+ const { data, error } = await authClient.lead.resend();
130
+ ```
131
+
132
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
133
+
101
134
  ### Update
102
135
 
136
+ Update the metadata of the lead associated with the currently authenticated user. Requires a valid session — the lead is looked up by the session user's `id`:
137
+
103
138
  ```ts
104
139
  // POST /lead/update
105
140
  const { data, error } = await authClient.lead.update({
106
- id: 'lead-id',
107
141
  metadata: {
108
142
  preferences: 'ai',
109
143
  },
110
144
  });
111
145
  ```
112
146
 
113
- ### Email Verification
147
+ If no session is present the endpoint responds with `401 Unauthorized`.
148
+
149
+ ### Admin
150
+
151
+ The lead plugin ships with a set of admin-only endpoints to manage leads: `listLead`, `getLead`, and `removeLead`. They are only available when:
152
+
153
+ 1. The better-auth [`admin`](https://www.better-auth.com/docs/plugins/admin) plugin is registered.
154
+ 2. The `admin.enabled` lead option is set to `true`.
155
+
156
+ Enable them in your auth config:
157
+
158
+ ```ts
159
+ // server/auth.ts
160
+ import { betterAuth } from 'better-auth';
161
+ import { admin } from 'better-auth/plugins';
162
+ import { lead } from 'better-auth-lead';
163
+
164
+ export const auth = betterAuth({
165
+ plugins: [
166
+ admin(),
167
+ lead({
168
+ admin: {
169
+ enabled: true,
170
+ // Optional. Roles allowed to call the admin endpoints.
171
+ // Default: ['admin'].
172
+ // Checked against session.user.role (the admin plugin supports
173
+ // comma-separated roles).
174
+ roles: ['admin', 'editor'],
175
+ },
176
+ }),
177
+ ],
178
+ });
179
+ ```
180
+
181
+ #### List leads
182
+
183
+ List a page of leads with optional search, filter, sort, and pagination. Requires a session with a role in `admin.roles`.
184
+
185
+ ```ts
186
+ // GET /lead/list-leads
187
+ const { data, error } = await authClient.lead.listLeads({
188
+ query: {
189
+ searchValue: 'user@example.com', // optional
190
+ searchField: 'email', // optional, default 'email'
191
+ searchOperator: 'contains', // optional: 'contains' | 'starts_with' | 'ends_with'
192
+ filterField: 'confirmed', // optional
193
+ filterValue: true, // optional
194
+ filterOperator: 'eq', // optional: any better-auth where operator
195
+ sortBy: 'createdAt', // optional
196
+ sortDirection: 'desc', // optional: 'asc' | 'desc'
197
+ limit: 100, // optional, default 100
198
+ offset: 0, // optional, default 0
199
+ },
200
+ });
201
+ ```
202
+
203
+ The response includes the page of `leads`, the `total` number of leads in the database, and the resolved `limit` and `offset` for client-side pagination.
204
+
205
+ #### Get a lead
206
+
207
+ Fetch a single lead by `id`. Requires a session with a role in `admin.roles`.
208
+
209
+ ```ts
210
+ // POST /lead/get-lead
211
+ const { data, error } = await authClient.lead.getLead({
212
+ query: {
213
+ id: 'lead-id',
214
+ },
215
+ });
216
+ ```
217
+
218
+ Returns the lead object, or `404 Not Found` (`LEAD_NOT_FOUND`) if no lead matches the id.
219
+
220
+ #### Remove a lead
221
+
222
+ Delete a single lead by `id`. Requires a session with a role in `admin.roles`.
223
+
224
+ ```ts
225
+ // POST /lead/remove-lead
226
+ const { data, error } = await authClient.lead.removeLead({
227
+ body: {
228
+ leadId: 'lead-id',
229
+ },
230
+ });
231
+ ```
114
232
 
115
- To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` takes a data object with the following properties:
233
+ Returns `{ success: true }` on success, or `404 Not Found` (`LEAD_NOT_FOUND`) if no lead matches the id.
234
+
235
+ #### Responses
236
+
237
+ All admin endpoints share the following responses:
238
+
239
+ - `404 Not Found` (`ADMIN_PLUGIN_REQUIRED`) if the admin plugin is not registered or `admin.enabled` is not set.
240
+ - `403 Forbidden` (`FORBIDDEN`) if the session user's role is not in `admin.roles`.
241
+ - `401 Unauthorized` if no session is present.
242
+
243
+ ### Email Confirmation
244
+
245
+ To enable double opt-in email confirmation, pass a `sendConfirmationEmail` function. It receives a data object with:
116
246
 
117
247
  - `lead`: The lead object.
118
- - `url`: The URL to send to the user which contains the token.
119
- - `token`: A verification token used to complete the email verification.
248
+ - `email`: The lead's email address.
249
+ - `url`: The URL containing the confirmation token to send to the user.
250
+ - `token`: The confirmation token used to complete the verification.
120
251
  - `unsubscribeUrl`: The endpoint URL for one-click unsubscribe (RFC 8058). Use this in `List-Unsubscribe` email headers.
121
252
 
122
253
  and a `request` object as the second parameter.
@@ -130,22 +261,22 @@ import { sendEmail } from './email'; // your email sending function
130
261
  export const auth = betterAuth({
131
262
  plugins: [
132
263
  lead({
133
- sendVerificationEmail: async ({ lead, url, token, unsubscribeUrl }) => {
134
- const { verificationEmailSentAt } = lead;
264
+ sendConfirmationEmail: async ({ lead, email, url, token, unsubscribeUrl }) => {
265
+ const { confirmationSentAt } = lead;
135
266
  if (
136
- verificationEmailSentAt &&
137
- Date.now() - verificationEmailSentAt.getTime() < 60 * 1000 // 1 minute
267
+ confirmationSentAt &&
268
+ Date.now() - confirmationSentAt.getTime() < 60 * 1000 // 1 minute
138
269
  ) {
139
270
  console.log(
140
- `Skipping sending verification email to ${lead.email} because a recent email was already sent.`,
271
+ `Skipping sending confirmation email to ${email} because a recent email was already sent.`,
141
272
  );
142
273
  return false;
143
274
  }
144
275
 
145
276
  void sendEmail({
146
- to: lead.email,
147
- subject: 'Newsletter: Verify your email address',
148
- text: `Click the link to verify your email: ${url}`,
277
+ to: email,
278
+ subject: 'Newsletter: Confirm your subscription',
279
+ text: `Click the link to confirm your subscription: ${url}`,
149
280
  // One-click unsubscribe headers (RFC 8058)
150
281
  // Supported by Gmail, Apple Mail, and Yahoo Mail.
151
282
  headers: {
@@ -156,9 +287,9 @@ export const auth = betterAuth({
156
287
 
157
288
  return true;
158
289
  },
159
- onEmailVerified: async ({ lead }) => {
160
- // do something when a lead's email is verified
161
- console.log(`Lead ${lead.email} has been verified!`);
290
+ onConfirmed: async ({ lead }) => {
291
+ // do something when a lead confirms their subscription
292
+ console.log(`Lead ${lead} has confirmed their subscription!`);
162
293
  },
163
294
  }),
164
295
  ],
@@ -167,7 +298,7 @@ export const auth = betterAuth({
167
298
 
168
299
  > Avoid awaiting the email sending to prevent timing attacks.
169
300
 
170
- Additionally, you can provide an `onEmailVerified` callback to execute logic after a lead's email is verified.
301
+ Additionally, you can provide an `onConfirmed` callback to execute logic after a lead confirms their subscription.
171
302
 
172
303
  ### Metadata Validation
173
304
 
@@ -218,6 +349,11 @@ await authClient.lead.subscribe({
218
349
  email: 'user@example.com',
219
350
  metadata: { preferences: 'engineering' },
220
351
  });
352
+
353
+ // or for the currently authenticated user (omit email)
354
+ await authClient.lead.subscribe({
355
+ metadata: { preferences: 'engineering' },
356
+ });
221
357
  ```
222
358
 
223
359
  ## Schema
@@ -226,29 +362,33 @@ await authClient.lead.subscribe({
226
362
 
227
363
  Table name: `lead`
228
364
 
229
- |  Field |  Type |  Key |  Description |
230
- | ----------------------- | ------- | ------ | ------------------------------------------------- |
231
- | id | string | pk | Unique identifier for each lead |
232
- | email | string | unique | Email address of the lead |
233
- | emailVerified | boolean | | Whether the email is verified |
234
- | verificationEmailSentAt | Date | ? | Timestamp of when the verification email was sent |
235
- | metadata | json | ? | Additional data about the lead |
236
- | createdAt | date | | Timestamp of lead creation |
237
- | updatedAt | date | | Timestamp of last update |
365
+ | Field | Type | Key | Description |
366
+ | ------------------ | ------- | ------ | ------------------------------------------------- |
367
+ | id | string | pk | Unique identifier for each lead |
368
+ | email | string? | unique | Email address of the lead (optional) |
369
+ | userId | string? | unique | ID of an associated better-auth user (optional) |
370
+ | confirmed | boolean | | Whether the lead has confirmed their subscription |
371
+ | confirmationSentAt | Date | ? | Timestamp of when the confirmation email was sent |
372
+ | metadata | json | ? | Additional data about the lead |
373
+ | createdAt | date | | Timestamp of lead creation |
374
+ | updatedAt | date | | Timestamp of last update |
238
375
 
239
376
  #### Prisma
240
377
 
241
378
  ```prisma
242
379
  model Lead {
243
- id String @id
244
- createdAt DateTime @default(now())
245
- updatedAt DateTime @updatedAt
246
- email String
247
- emailVerified Boolean @default(false)
248
- verificationEmailSentAt DateTime?
249
- metadata String?
380
+ id String @id
381
+ createdAt DateTime @default(now())
382
+ updatedAt DateTime @updatedAt
383
+ email String?
384
+ userId String?
385
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
386
+ confirmed Boolean @default(false)
387
+ confirmationSentAt DateTime?
388
+ metadata String?
250
389
 
251
390
  @@unique([email])
391
+ @@unique([userId])
252
392
  @@map("lead")
253
393
  }
254
394
  ```
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as LeadOptions, t as lead } from "./index-P6m89iLp.mjs";
1
+ import { r as LeadOptions, t as lead } from "./index-BX1SlAVz.mjs";
2
2
  import { StandardSchemaV1 } from "better-auth";
3
3
 
4
4
  //#region src/client.d.ts