@rebasepro/cli 0.6.0 → 0.7.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.
Files changed (51) hide show
  1. package/README.md +0 -1
  2. package/dist/commands/api-keys.d.ts +1 -0
  3. package/dist/commands/generate_sdk.d.ts +1 -1
  4. package/dist/commands/init.d.ts +13 -0
  5. package/dist/index.es.js +463 -105
  6. package/dist/index.es.js.map +1 -1
  7. package/package.json +22 -18
  8. package/templates/template/backend/package.json +0 -1
  9. package/templates/template/backend/src/index.ts +1 -1
  10. package/templates/template/config/collections/posts.ts +1 -3
  11. package/templates/template/config/collections/tags.ts +1 -3
  12. package/templates/template/config/collections/users.ts +1 -4
  13. package/templates/template/package.json +0 -1
  14. package/templates/template/scripts/example.ts +4 -1
  15. package/dist/index.cjs +0 -1948
  16. package/dist/index.cjs.map +0 -1
  17. package/skills/rebase-api/SKILL.md +0 -662
  18. package/skills/rebase-api/references/.gitkeep +0 -3
  19. package/skills/rebase-auth/SKILL.md +0 -1143
  20. package/skills/rebase-auth/references/.gitkeep +0 -3
  21. package/skills/rebase-backend-postgres/SKILL.md +0 -633
  22. package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
  23. package/skills/rebase-basics/SKILL.md +0 -749
  24. package/skills/rebase-basics/references/.gitkeep +0 -3
  25. package/skills/rebase-collections/SKILL.md +0 -1328
  26. package/skills/rebase-collections/references/.gitkeep +0 -3
  27. package/skills/rebase-cron-jobs/SKILL.md +0 -699
  28. package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
  29. package/skills/rebase-custom-functions/SKILL.md +0 -233
  30. package/skills/rebase-deployment/SKILL.md +0 -583
  31. package/skills/rebase-deployment/references/.gitkeep +0 -3
  32. package/skills/rebase-design-language/SKILL.md +0 -664
  33. package/skills/rebase-email/SKILL.md +0 -701
  34. package/skills/rebase-email/references/.gitkeep +0 -1
  35. package/skills/rebase-entity-history/SKILL.md +0 -485
  36. package/skills/rebase-entity-history/references/.gitkeep +0 -1
  37. package/skills/rebase-local-env-setup/SKILL.md +0 -189
  38. package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
  39. package/skills/rebase-realtime/SKILL.md +0 -755
  40. package/skills/rebase-realtime/references/.gitkeep +0 -3
  41. package/skills/rebase-sdk/SKILL.md +0 -594
  42. package/skills/rebase-sdk/references/.gitkeep +0 -0
  43. package/skills/rebase-storage/SKILL.md +0 -765
  44. package/skills/rebase-storage/references/.gitkeep +0 -3
  45. package/skills/rebase-studio/SKILL.md +0 -746
  46. package/skills/rebase-studio/references/.gitkeep +0 -3
  47. package/skills/rebase-ui-components/SKILL.md +0 -1411
  48. package/skills/rebase-ui-components/references/.gitkeep +0 -3
  49. package/skills/rebase-webhooks/SKILL.md +0 -623
  50. package/skills/rebase-webhooks/references/.gitkeep +0 -1
  51. package/templates/template/backend/drizzle.config.ts +0 -51
@@ -1,701 +0,0 @@
1
- ---
2
- name: rebase-email
3
- description: Guide for configuring and using the Rebase email system. Use this skill when the user needs to set up SMTP, customize email templates, use a custom email provider (AWS SES, Resend, Postmark), send emails programmatically via the `rebase.email` singleton, or understand how email integrates with password reset, email verification, user invitation, and welcome email flows.
4
- ---
5
-
6
- # Rebase Email
7
-
8
- Rebase includes a built-in email system that powers password reset, email verification, user invitation, and welcome email flows. It supports SMTP (via Nodemailer), custom send functions for third-party providers (AWS SES, Resend, Postmark, etc.), and fully customizable HTML/text templates.
9
-
10
- > **IMPORTANT FOR AGENTS:** Always read the `rebase-basics` skill first. Email requires a running Rebase backend with `initializeRebaseBackend()` and is configured through the `auth.email` property.
11
-
12
- ## EmailConfig Interface
13
-
14
- Email is configured via the `email` property inside `auth` in `initializeRebaseBackend()`:
15
-
16
- ```typescript
17
- import { initializeRebaseBackend } from "@rebasepro/server-core";
18
-
19
- const backend = await initializeRebaseBackend({
20
- server, app,
21
- database: postgresAdapter,
22
- auth: {
23
- collection: usersCollection,
24
- jwtSecret: env.JWT_SECRET,
25
- email: {
26
- from: "MyApp <noreply@myapp.com>",
27
- smtp: {
28
- host: "smtp.gmail.com",
29
- port: 587,
30
- auth: { user: "user@gmail.com", pass: "app-password" },
31
- },
32
- appName: "MyApp",
33
- resetPasswordUrl: "https://myapp.com",
34
- verifyEmailUrl: "https://myapp.com",
35
- },
36
- },
37
- });
38
- ```
39
-
40
- ### EmailConfig Properties
41
-
42
- | Property | Type | Default | Description |
43
- |----------|------|---------|-------------|
44
- | `from` | `string` | — | **Required.** Sender address. Format: `"noreply@example.com"` or `"MyApp <noreply@example.com>"` |
45
- | `smtp` | `SMTPConfig` | `undefined` | SMTP server configuration (see below) |
46
- | `sendEmail` | `(options: EmailSendOptions) => Promise<void>` | `undefined` | Custom send function — replaces SMTP entirely |
47
- | `resetPasswordUrl` | `string` | `""` | Base URL for password reset links. The reset link becomes `{resetPasswordUrl}/reset-password?token={token}` |
48
- | `verifyEmailUrl` | `string` | `""` | Base URL for email verification links. The link becomes `{verifyEmailUrl}/verify-email?token={token}` |
49
- | `appName` | `string` | `"Rebase"` | Application name used in email subjects and body text |
50
- | `templates` | `object` | `undefined` | Custom template functions to override built-in templates (see [Custom Templates](#custom-templates)) |
51
-
52
- > **IMPORTANT FOR AGENTS:** You must provide **either** `smtp` or `sendEmail` (not both). If neither is provided, the email service reports `isConfigured() === false` and all email-dependent auth flows (forgot-password, send-verification, user invitation) will fail with a `503 EMAIL_NOT_CONFIGURED` error.
53
-
54
- ### SMTPConfig Properties
55
-
56
- | Property | Type | Default | Description |
57
- |----------|------|---------|-------------|
58
- | `host` | `string` | — | **Required.** SMTP server hostname (e.g., `"smtp.gmail.com"`) |
59
- | `port` | `number` | — | **Required.** SMTP server port (typically `587` for STARTTLS, `465` for SSL) |
60
- | `secure` | `boolean` | `true` when `port === 465`, otherwise `false` | Use SSL/TLS. Auto-inferred from port when not set |
61
- | `auth` | `{ user: string; pass: string }` | `undefined` | SMTP credentials. Omit for unauthenticated SMTP relays |
62
- | `name` | `string` | Auto-detected from `FRONTEND_URL` | Client hostname sent in SMTP `EHLO`/`HELO` greeting (see [SMTP Name Resolution](#smtp-name-resolution)) |
63
-
64
- ### EmailSendOptions
65
-
66
- The `EmailSendOptions` interface is used by both `emailService.send()` and custom `sendEmail` functions:
67
-
68
- | Property | Type | Required | Description |
69
- |----------|------|----------|-------------|
70
- | `to` | `string \| string[]` | Yes | Recipient email address(es) |
71
- | `subject` | `string` | Yes | Email subject line |
72
- | `html` | `string` | Yes | HTML body content |
73
- | `text` | `string` | No | Plain-text fallback body |
74
- | `replyTo` | `string` | No | Reply-to email address |
75
-
76
- ### EmailService Interface
77
-
78
- The `EmailService` interface is exposed on the `rebase.email` singleton:
79
-
80
- | Method | Signature | Description |
81
- |--------|-----------|-------------|
82
- | `send` | `(options: EmailSendOptions) => Promise<void>` | Send a single email |
83
- | `isConfigured` | `() => boolean` | Returns `true` when SMTP or a custom `sendEmail` function is configured |
84
- | `verifyConnection` | `() => Promise<boolean>` | *(Optional)* Test SMTP connectivity. Returns `true` on success, `false` on failure. For custom `sendEmail`, returns `true` if the function is set |
85
-
86
- ## SMTP Setup
87
-
88
- ### Basic SMTP Configuration
89
-
90
- ```typescript
91
- auth: {
92
- // ...
93
- email: {
94
- from: "noreply@myapp.com",
95
- appName: "MyApp",
96
- smtp: {
97
- host: "smtp.gmail.com",
98
- port: 587,
99
- auth: {
100
- user: "user@gmail.com",
101
- pass: "your-app-password",
102
- },
103
- },
104
- resetPasswordUrl: "https://myapp.com",
105
- verifyEmailUrl: "https://myapp.com",
106
- },
107
- }
108
- ```
109
-
110
- ### Common SMTP Providers
111
-
112
- ```typescript
113
- // Gmail (requires App Password with 2FA enabled)
114
- smtp: { host: "smtp.gmail.com", port: 587, auth: { user: "you@gmail.com", pass: "xxxx xxxx xxxx xxxx" } }
115
-
116
- // Amazon SES
117
- smtp: { host: "email-smtp.us-east-1.amazonaws.com", port: 587, auth: { user: "AKIAIOSFODNN7EXAMPLE", pass: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } }
118
-
119
- // Resend SMTP
120
- smtp: { host: "smtp.resend.com", port: 465, secure: true, auth: { user: "resend", pass: "re_xxxxxxxxxxxx" } }
121
-
122
- // Postmark
123
- smtp: { host: "smtp.postmarkapp.com", port: 587, auth: { user: "your-server-api-token", pass: "your-server-api-token" } }
124
-
125
- // Mailtrap (testing)
126
- smtp: { host: "sandbox.smtp.mailtrap.io", port: 2525, auth: { user: "your-mailtrap-user", pass: "your-mailtrap-pass" } }
127
-
128
- // SendGrid
129
- smtp: { host: "smtp.sendgrid.net", port: 587, auth: { user: "apikey", pass: "SG.xxxxxxxxxxxxx" } }
130
-
131
- // Mailgun
132
- smtp: { host: "smtp.mailgun.org", port: 587, auth: { user: "postmaster@your-domain.mailgun.org", pass: "your-mailgun-key" } }
133
- ```
134
-
135
- ### Using Environment Variables for SMTP
136
-
137
- A common pattern is to use `loadEnv` with extended variables:
138
-
139
- ```typescript
140
- import { loadEnv } from "@rebasepro/server-core";
141
- import { z } from "zod";
142
-
143
- export const env = loadEnv({
144
- extend: z.object({
145
- SMTP_HOST: z.string().optional(),
146
- SMTP_PORT: z.string().default("587").transform(Number),
147
- SMTP_SECURE: z.enum(["true", "false", ""]).default("false").transform(v => v === "true"),
148
- SMTP_USER: z.string().optional(),
149
- SMTP_PASS: z.string().optional(),
150
- SMTP_FROM: z.string().default("noreply@example.com"),
151
- }),
152
- });
153
-
154
- // Then in your backend config:
155
- auth: {
156
- // ...
157
- email: {
158
- from: env.SMTP_FROM,
159
- appName: "MyApp",
160
- smtp: env.SMTP_HOST ? {
161
- host: env.SMTP_HOST,
162
- port: env.SMTP_PORT,
163
- secure: env.SMTP_SECURE,
164
- auth: env.SMTP_USER ? {
165
- user: env.SMTP_USER,
166
- pass: env.SMTP_PASS!,
167
- } : undefined,
168
- } : undefined,
169
- resetPasswordUrl: env.FRONTEND_URL || "http://localhost:3000",
170
- verifyEmailUrl: env.FRONTEND_URL || "http://localhost:3000",
171
- },
172
- }
173
- ```
174
-
175
- > **IMPORTANT FOR AGENTS:** SMTP-related variables (`SMTP_HOST`, `SMTP_PORT`, etc.) are **NOT** part of the base Rebase environment schema. They must be added via `loadEnv({ extend: ... })`. The base schema does include `FRONTEND_URL` which is used for SMTP name resolution.
176
-
177
- ### `.env` File Example
178
-
179
- ```env
180
- # SMTP Configuration
181
- SMTP_HOST=smtp.gmail.com
182
- SMTP_PORT=587
183
- SMTP_SECURE=false
184
- SMTP_USER=noreply@myapp.com
185
- SMTP_PASS=your-app-password
186
- SMTP_FROM=MyApp <noreply@myapp.com>
187
-
188
- # Used for password reset / verification links AND SMTP EHLO name
189
- FRONTEND_URL=https://myapp.com
190
- ```
191
-
192
- ## Built-in Email Templates
193
-
194
- Rebase ships four built-in HTML email templates. All templates include both HTML and plain-text versions, use responsive inline CSS, and follow a consistent card-based design with a blue (#3b82f6) call-to-action button.
195
-
196
- ### Template Overview
197
-
198
- | Template | Trigger | Subject | Token Expiry |
199
- |----------|---------|---------|-------------|
200
- | Password Reset | `POST /api/auth/forgot-password` | `"Reset your {appName} password"` | 1 hour |
201
- | Email Verification | `POST /api/auth/send-verification` | `"Verify your {appName} email address"` | No expiry (stored as user field) |
202
- | User Invitation | `POST /api/admin/users` (admin-created user, no explicit password) | `"You've been invited to {appName}"` | 1 hour (uses password reset token) |
203
- | Welcome Email | `POST /api/auth/register` and OAuth first-login | `"¡Bienvenido/a a {appName}!"` | N/A (informational) |
204
-
205
- ### Template Functions
206
-
207
- Each built-in template is a standalone function exported from `@rebasepro/server-core`:
208
-
209
- ```typescript
210
- import {
211
- getPasswordResetTemplate,
212
- getEmailVerificationTemplate,
213
- getUserInvitationTemplate,
214
- getWelcomeEmailTemplate,
215
- } from "@rebasepro/server-core";
216
- ```
217
-
218
- #### getPasswordResetTemplate
219
-
220
- ```typescript
221
- function getPasswordResetTemplate(
222
- resetUrl: string,
223
- user: { email: string; displayName?: string | null },
224
- appName?: string // default: "Rebase"
225
- ): { subject: string; html: string; text: string }
226
- ```
227
-
228
- The template includes a "Reset Password" button linking to `resetUrl`, a plain-text fallback with the URL, and a warning that the link expires in 1 hour.
229
-
230
- #### getEmailVerificationTemplate
231
-
232
- ```typescript
233
- function getEmailVerificationTemplate(
234
- verifyUrl: string,
235
- user: { email: string; displayName?: string | null },
236
- appName?: string // default: "Rebase"
237
- ): { subject: string; html: string; text: string }
238
- ```
239
-
240
- The template includes a "Verify Email Address" button linking to `verifyUrl`.
241
-
242
- #### getUserInvitationTemplate
243
-
244
- ```typescript
245
- function getUserInvitationTemplate(
246
- setPasswordUrl: string,
247
- user: { email: string; displayName?: string | null },
248
- appName?: string // default: "Rebase"
249
- ): { subject: string; html: string; text: string }
250
- ```
251
-
252
- Sent when an admin creates a user via `POST /api/admin/users` without specifying a password. The template includes a "Set Your Password" button and a 1-hour expiry warning.
253
-
254
- #### getWelcomeEmailTemplate
255
-
256
- ```typescript
257
- function getWelcomeEmailTemplate(
258
- user: { email: string; displayName?: string | null },
259
- appName?: string, // default: "Rebase"
260
- loginUrl?: string
261
- ): { subject: string; html: string; text: string }
262
- ```
263
-
264
- > **IMPORTANT FOR AGENTS:** The default welcome email template is in Spanish. The subject is `"¡Bienvenido/a a {appName}!"` and the body is Spanish text. Override this template if you need a different language.
265
-
266
- ### Template User Greeting
267
-
268
- All templates greet the user by `displayName` if available, otherwise by the email username (the part before `@`). For example, `user@example.com` is greeted as `"Hi user"`.
269
-
270
- ## Custom Templates
271
-
272
- Override any built-in template by providing a function in `emailConfig.templates`:
273
-
274
- ```typescript
275
- auth: {
276
- email: {
277
- from: "noreply@myapp.com",
278
- appName: "MyApp",
279
- smtp: { /* ... */ },
280
- resetPasswordUrl: "https://myapp.com",
281
- verifyEmailUrl: "https://myapp.com",
282
- templates: {
283
- passwordReset: (resetUrl, user) => ({
284
- subject: `Reset your password, ${user.displayName || user.email}`,
285
- html: `<h1>Password Reset</h1><p>Click <a href="${resetUrl}">here</a> to reset.</p>`,
286
- text: `Reset your password: ${resetUrl}`,
287
- }),
288
- emailVerification: (verifyUrl, user) => ({
289
- subject: "Please verify your email",
290
- html: `<p>Hi ${user.displayName || user.email}, <a href="${verifyUrl}">verify here</a></p>`,
291
- }),
292
- userInvitation: (setPasswordUrl, user) => ({
293
- subject: "You've been invited!",
294
- html: `<p>Welcome! Set your password: <a href="${setPasswordUrl}">${setPasswordUrl}</a></p>`,
295
- }),
296
- welcomeEmail: (user, appName) => ({
297
- subject: `Welcome to ${appName}!`,
298
- html: `<h1>Welcome, ${user.displayName || user.email}!</h1><p>Thanks for joining ${appName}.</p>`,
299
- text: `Welcome to ${appName}!`,
300
- }),
301
- },
302
- },
303
- }
304
- ```
305
-
306
- ### Template Function Signatures
307
-
308
- | Template Key | Signature |
309
- |-------------|-----------|
310
- | `passwordReset` | `(resetUrl: string, user: { email: string; displayName?: string \| null }) => { subject: string; html: string; text?: string }` |
311
- | `emailVerification` | `(verifyUrl: string, user: { email: string; displayName?: string \| null }) => { subject: string; html: string; text?: string }` |
312
- | `userInvitation` | `(setPasswordUrl: string, user: { email: string; displayName?: string \| null }) => { subject: string; html: string; text?: string }` |
313
- | `welcomeEmail` | `(user: { email: string; displayName?: string \| null }, appName: string) => { subject: string; html: string; text?: string }` |
314
-
315
- > **IMPORTANT FOR AGENTS:** The `text` field is optional in custom templates but recommended for deliverability. Many spam filters penalize emails without a plain-text alternative.
316
-
317
- ## Custom Send Functions
318
-
319
- Replace SMTP entirely with a custom `sendEmail` function for third-party email APIs:
320
-
321
- ### AWS SES Example
322
-
323
- ```typescript
324
- import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
325
-
326
- const ses = new SESClient({ region: "us-east-1" });
327
-
328
- auth: {
329
- email: {
330
- from: "noreply@myapp.com",
331
- appName: "MyApp",
332
- resetPasswordUrl: "https://myapp.com",
333
- verifyEmailUrl: "https://myapp.com",
334
- sendEmail: async (options) => {
335
- const to = Array.isArray(options.to) ? options.to : [options.to];
336
- await ses.send(new SendEmailCommand({
337
- Source: "noreply@myapp.com",
338
- Destination: { ToAddresses: to },
339
- Message: {
340
- Subject: { Data: options.subject },
341
- Body: {
342
- Html: { Data: options.html },
343
- ...(options.text ? { Text: { Data: options.text } } : {}),
344
- },
345
- },
346
- ...(options.replyTo ? { ReplyToAddresses: [options.replyTo] } : {}),
347
- }));
348
- },
349
- },
350
- }
351
- ```
352
-
353
- ### Resend API Example
354
-
355
- ```typescript
356
- import { Resend } from "resend";
357
-
358
- const resend = new Resend("re_xxxxxxxxxxxx");
359
-
360
- auth: {
361
- email: {
362
- from: "MyApp <noreply@myapp.com>",
363
- appName: "MyApp",
364
- resetPasswordUrl: "https://myapp.com",
365
- verifyEmailUrl: "https://myapp.com",
366
- sendEmail: async (options) => {
367
- await resend.emails.send({
368
- from: "MyApp <noreply@myapp.com>",
369
- to: Array.isArray(options.to) ? options.to : [options.to],
370
- subject: options.subject,
371
- html: options.html,
372
- text: options.text,
373
- reply_to: options.replyTo,
374
- });
375
- },
376
- },
377
- }
378
- ```
379
-
380
- ### Postmark API Example
381
-
382
- ```typescript
383
- import { ServerClient } from "postmark";
384
-
385
- const postmark = new ServerClient("your-server-api-token");
386
-
387
- auth: {
388
- email: {
389
- from: "noreply@myapp.com",
390
- appName: "MyApp",
391
- resetPasswordUrl: "https://myapp.com",
392
- verifyEmailUrl: "https://myapp.com",
393
- sendEmail: async (options) => {
394
- await postmark.sendEmail({
395
- From: "noreply@myapp.com",
396
- To: Array.isArray(options.to) ? options.to.join(",") : options.to,
397
- Subject: options.subject,
398
- HtmlBody: options.html,
399
- TextBody: options.text || "",
400
- ReplyTo: options.replyTo,
401
- });
402
- },
403
- },
404
- }
405
- ```
406
-
407
- > **IMPORTANT FOR AGENTS:** When using `sendEmail`, the `from` field in `EmailConfig` is **not** automatically applied by the email service. Your custom function receives the `EmailSendOptions` (which has `to`, `subject`, `html`, `text`, `replyTo` but **no** `from`). You must set the sender address inside your function.
408
-
409
- ## `rebase.email` Singleton
410
-
411
- When email is configured, the email service is automatically attached to the `rebase` server-side singleton as `rebase.email`. This allows sending emails programmatically from custom functions, cron jobs, and entity callbacks.
412
-
413
- ```typescript
414
- import { rebase } from "@rebasepro/server-core";
415
-
416
- // Check if email is available
417
- if (rebase.email) {
418
- await rebase.email.send({
419
- to: "user@example.com",
420
- subject: "Your order has shipped",
421
- html: "<h1>Order Shipped!</h1><p>Your order #12345 is on its way.</p>",
422
- text: "Your order #12345 has shipped.",
423
- });
424
- }
425
- ```
426
-
427
- ### Using in Custom Functions
428
-
429
- ```typescript
430
- // functions/send-report.ts
431
- import { defineFunction } from "@rebasepro/server-core";
432
-
433
- export default defineFunction({
434
- method: "POST",
435
- handler: async (ctx) => {
436
- const { email, reportData } = ctx.body;
437
-
438
- if (!ctx.client.email) {
439
- return ctx.json({ error: "Email not configured" }, 503);
440
- }
441
-
442
- await ctx.client.email.send({
443
- to: email,
444
- subject: "Your Monthly Report",
445
- html: `<h1>Report</h1><pre>${JSON.stringify(reportData, null, 2)}</pre>`,
446
- });
447
-
448
- return ctx.json({ success: true, message: "Report sent" });
449
- },
450
- });
451
- ```
452
-
453
- ### Using in Cron Jobs
454
-
455
- ```typescript
456
- // crons/weekly-digest.ts
457
- import { defineCron } from "@rebasepro/server-core";
458
-
459
- export default defineCron({
460
- schedule: "0 9 * * 1", // Every Monday at 9 AM
461
- handler: async (ctx) => {
462
- if (!ctx.client.email) {
463
- console.warn("Email not configured, skipping digest");
464
- return;
465
- }
466
-
467
- // Fetch subscribers
468
- const { data: subscribers } = await ctx.client.data
469
- .collection("subscribers")
470
- .get({ filter: { active: true } });
471
-
472
- for (const sub of subscribers) {
473
- await ctx.client.email.send({
474
- to: sub.email,
475
- subject: "Your Weekly Digest",
476
- html: "<h1>This Week's Updates</h1>...",
477
- });
478
- }
479
- },
480
- });
481
- ```
482
-
483
- ### Checking Email Configuration
484
-
485
- ```typescript
486
- import { rebase } from "@rebasepro/server-core";
487
-
488
- // rebase.email is undefined when no email config is provided at all
489
- if (!rebase.email) {
490
- console.log("Email not configured");
491
- }
492
-
493
- // rebase.email exists but may not be functional
494
- if (rebase.email && !rebase.email.isConfigured()) {
495
- console.log("Email service exists but has no SMTP or sendEmail function");
496
- }
497
-
498
- // Full check
499
- if (rebase.email?.isConfigured()) {
500
- console.log("Email is ready to send");
501
- }
502
- ```
503
-
504
- ## Integration with Auth Flows
505
-
506
- The email system is tightly integrated with Rebase authentication. Here is how each flow works:
507
-
508
- ### Password Reset Flow
509
-
510
- | Step | Endpoint | Description |
511
- |------|----------|-------------|
512
- | 1 | `POST /api/auth/forgot-password` | User submits `{ email }`. Server generates a SHA-256 hashed token, stores it with a 1-hour expiry, and sends the password reset email |
513
- | 2 | User clicks link | User is directed to `{resetPasswordUrl}/reset-password?token={token}` |
514
- | 3 | `POST /api/auth/reset-password` | Client submits `{ token, password }`. Server verifies the token, updates the password, marks the token as used, and invalidates all refresh tokens (logs out all sessions) |
515
-
516
- > **WARNING FOR AGENTS:** The `POST /api/auth/forgot-password` endpoint **always** returns `{ success: true }` regardless of whether the email exists. This is a security measure to prevent email enumeration. If the email service is not configured, it throws `503 EMAIL_NOT_CONFIGURED`.
517
-
518
- ### Email Verification Flow
519
-
520
- | Step | Endpoint | Description |
521
- |------|----------|-------------|
522
- | 1 | `POST /api/auth/send-verification` | Authenticated user requests verification. Server generates a token, stores the SHA-256 hash on the user record, and sends the verification email |
523
- | 2 | User clicks link | User is directed to `{verifyEmailUrl}/verify-email?token={token}` |
524
- | 3 | `GET /api/auth/verify-email?token={token}` | Server looks up the user by hashed token and sets `emailVerified = true` |
525
-
526
- The `send-verification` endpoint requires authentication and returns `400 ALREADY_VERIFIED` if the email is already verified.
527
-
528
- ### User Invitation Flow (Admin-Created Users)
529
-
530
- | Step | Endpoint | Description |
531
- |------|----------|-------------|
532
- | 1 | `POST /api/admin/users` | Admin creates user with `{ email, displayName, roles }` (no password). Server auto-generates a password, creates a password reset token, and sends the invitation email |
533
- | 2 | User clicks link | User is directed to `{resetPasswordUrl}/reset-password?token={token}` (same as password reset) |
534
- | 3 | `POST /api/auth/reset-password` | User sets their password via the standard reset flow |
535
-
536
- **Fallback behavior:** If the email service is not configured or the email fails to send, the API response includes a `temporaryPassword` field that the admin can share manually:
537
-
538
- ```json
539
- {
540
- "user": { "uid": "...", "email": "new@user.com", "roles": ["editor"] },
541
- "invitationSent": false,
542
- "temporaryPassword": "aB3xKm9pQr2sT5wY"
543
- }
544
- ```
545
-
546
- When the invitation email is sent successfully:
547
-
548
- ```json
549
- {
550
- "user": { "uid": "...", "email": "new@user.com", "roles": ["editor"] },
551
- "invitationSent": true
552
- }
553
- ```
554
-
555
- ### Admin Password Reset
556
-
557
- | Step | Endpoint | Description |
558
- |------|----------|-------------|
559
- | 1 | `POST /api/admin/users/:userId/reset-password` | Admin triggers a password reset for a specific user. If email is configured, sends a reset email. Otherwise, generates a new password and returns it as `temporaryPassword` |
560
-
561
- ### Welcome Email
562
-
563
- Sent automatically (fire-and-forget) when:
564
- - A user registers via `POST /api/auth/register`
565
- - A new user is created via an OAuth provider (first login)
566
-
567
- The welcome email is **not sent** when an admin creates a user via `POST /api/admin/users` (the invitation email is sent instead).
568
-
569
- The welcome email uses `resetPasswordUrl` as the base for a login link, appending `/app` (e.g., `https://myapp.com/app`).
570
-
571
- ## SMTP Name Resolution
572
-
573
- When creating the Nodemailer transport, Rebase automatically resolves a hostname for the SMTP `EHLO`/`HELO` greeting. This is important because some SMTP servers (e.g., Gmail, Outlook) reject connections from clients that identify as `localhost`.
574
-
575
- **Resolution order:**
576
-
577
- | Priority | Source | Description |
578
- |----------|--------|-------------|
579
- | 1 | `smtp.name` | Explicitly set in the `SMTPConfig` |
580
- | 2 | `FRONTEND_URL` env var | Hostname extracted from the URL |
581
- | 3 | `emailConfig.resetPasswordUrl` | Hostname extracted from the URL |
582
- | 4 | `emailConfig.verifyEmailUrl` | Hostname extracted from the URL |
583
-
584
- ```typescript
585
- // If FRONTEND_URL=https://myapp.com, the EHLO name will be "myapp.com"
586
- // The URL is parsed safely — invalid URLs are skipped
587
- ```
588
-
589
- > **IMPORTANT FOR AGENTS:** If you encounter SMTP delivery issues (especially with Gmail or corporate SMTP servers), ensure `FRONTEND_URL` is set to a valid URL with a real domain, or set `smtp.name` explicitly. Sending EHLO with `localhost` will cause many SMTP servers to reject the connection.
590
-
591
- ## SMTP Connection Verification
592
-
593
- At startup, Rebase automatically verifies the SMTP connection when email is configured. This is a non-blocking check that logs the result:
594
-
595
- - **Success:** `"SMTP connection verified successfully."`
596
- - **Failure:** `"Warning: SMTP connection verification failed. Email delivery may fail."`
597
-
598
- The verification calls Nodemailer's `transporter.verify()` which attempts to connect and authenticate with the SMTP server. **Startup is not blocked** — the verification runs asynchronously after the backend is initialized.
599
-
600
- When using a custom `sendEmail` function instead of SMTP, `verifyConnection()` simply returns `true` (there is no transport to verify).
601
-
602
- ## Complete Configuration Example
603
-
604
- ```typescript
605
- import { Hono } from "hono";
606
- import { serve } from "@hono/node-server";
607
- import { initializeRebaseBackend, loadEnv } from "@rebasepro/server-core";
608
- import { createPostgresAdapter } from "@rebasepro/server-postgresql";
609
- import { defaultUsersCollection } from "@rebasepro/common";
610
- import { z } from "zod";
611
-
612
- // 1. Load and validate environment
613
- const env = loadEnv({
614
- extend: z.object({
615
- SMTP_HOST: z.string().optional(),
616
- SMTP_PORT: z.string().default("587").transform(Number),
617
- SMTP_SECURE: z.enum(["true", "false", ""]).default("false").transform(v => v === "true"),
618
- SMTP_USER: z.string().optional(),
619
- SMTP_PASS: z.string().optional(),
620
- SMTP_FROM: z.string().default("noreply@example.com"),
621
- APP_NAME: z.string().default("MyApp"),
622
- }),
623
- });
624
-
625
- // 2. Set up Hono and HTTP server
626
- const app = new Hono();
627
- const server = serve({ fetch: app.fetch, port: env.PORT });
628
-
629
- // 3. Create database adapter
630
- const postgresAdapter = createPostgresAdapter({
631
- connectionString: env.DATABASE_URL,
632
- });
633
-
634
- // 4. Initialize backend with email
635
- await initializeRebaseBackend({
636
- server, app,
637
- database: postgresAdapter,
638
- auth: {
639
- collection: defaultUsersCollection,
640
- jwtSecret: env.JWT_SECRET,
641
- serviceKey: env.REBASE_SERVICE_KEY,
642
- allowRegistration: env.ALLOW_REGISTRATION,
643
- email: {
644
- from: env.SMTP_FROM,
645
- appName: env.APP_NAME,
646
- smtp: env.SMTP_HOST ? {
647
- host: env.SMTP_HOST,
648
- port: env.SMTP_PORT,
649
- secure: env.SMTP_SECURE,
650
- auth: env.SMTP_USER ? {
651
- user: env.SMTP_USER,
652
- pass: env.SMTP_PASS!,
653
- } : undefined,
654
- } : undefined,
655
- resetPasswordUrl: env.FRONTEND_URL || "http://localhost:3000",
656
- verifyEmailUrl: env.FRONTEND_URL || "http://localhost:3000",
657
- templates: {
658
- // Override the default Spanish welcome email
659
- welcomeEmail: (user, appName) => ({
660
- subject: `Welcome to ${appName}!`,
661
- html: `<h1>Welcome!</h1><p>Hi ${user.displayName || user.email}, thanks for joining ${appName}.</p>`,
662
- text: `Welcome to ${appName}! Thanks for joining.`,
663
- }),
664
- },
665
- },
666
- },
667
- });
668
-
669
- console.log(`Server running on port ${env.PORT}`);
670
- ```
671
-
672
- ## Error Handling
673
-
674
- ### Email Service Errors
675
-
676
- | Error | Condition | HTTP Status |
677
- |-------|-----------|-------------|
678
- | `"Email service not configured. Password reset is not available."` | `POST /api/auth/forgot-password` when no email configured | 503 |
679
- | `"Email service not configured. Email verification is not available."` | `POST /api/auth/send-verification` when no email configured | 503 |
680
- | `"Email is already verified"` | `POST /api/auth/send-verification` for a verified user | 400 |
681
- | `"Invalid or expired reset token"` | `POST /api/auth/reset-password` with bad/expired token | 400 |
682
- | `"Invalid or expired verification token"` | `GET /api/auth/verify-email` with bad token | 400 |
683
- | `"Email service not configured. Provide SMTP config or sendEmail function."` | Calling `emailService.send()` with no transport | Error thrown |
684
- | `"Failed to send email: {message}"` | SMTP send failure | Error thrown |
685
-
686
- ### Silent Failures
687
-
688
- The following email failures are logged but do **not** cause the API request to fail:
689
-
690
- - **Password reset email:** If `send()` throws, the error is logged but `POST /api/auth/forgot-password` still returns `{ success: true }` (security: don't reveal failures)
691
- - **Welcome email:** Sent fire-and-forget — failures are logged but don't block registration
692
- - **User invitation email:** If `send()` throws, the response falls back to returning `temporaryPassword`
693
-
694
- ## References
695
-
696
- - **Documentation:** [rebase.pro/docs](https://rebase.pro/docs)
697
- - **GitHub:** [github.com/rebasepro/rebase](https://github.com/rebasepro/rebase)
698
- - **Nodemailer:** [nodemailer.com](https://nodemailer.com)
699
- - **AWS SES SDK:** [docs.aws.amazon.com/ses](https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html)
700
- - **Resend:** [resend.com/docs](https://resend.com/docs)
701
- - **Postmark:** [postmarkapp.com/developer](https://postmarkapp.com/developer)