needware-cli 1.5.22 → 1.5.25

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.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Add Secret Tool
3
+ * Tool for adding secrets to Supabase project
4
+ */
5
+ import { BaseTool } from './base-tool.js';
6
+ import { ToolDefinition, ToolResult } from '../types/tool.js';
7
+ import { z } from 'zod';
8
+ export declare class AddSecretTool extends BaseTool {
9
+ definition: ToolDefinition;
10
+ getZodSchema(): {
11
+ placeholder: z.ZodString;
12
+ };
13
+ execute(input: Record<string, any>): Promise<ToolResult>;
14
+ }
15
+ //# sourceMappingURL=add-secret-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-secret-tool.d.ts","sourceRoot":"","sources":["../../src/tools/add-secret-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,qBAAa,aAAc,SAAQ,QAAQ;IACzC,UAAU,EAAE,cAAc,CAaxB;IAEF,YAAY;;;IAMN,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;CA4B/D"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Add Secret Tool
3
+ * Tool for adding secrets to Supabase project
4
+ */
5
+ import { BaseTool } from './base-tool.js';
6
+ import chalk from 'chalk';
7
+ import { z } from 'zod';
8
+ export class AddSecretTool extends BaseTool {
9
+ definition = {
10
+ name: 'add-secret',
11
+ description: 'Add a secret (environment variable) to the Supabase project for use in Edge Functions',
12
+ input_schema: {
13
+ type: 'object',
14
+ properties: {
15
+ placeholder: {
16
+ type: 'string',
17
+ description: 'The placeholder of the secret (environment variable), e.g., RESEND_API_KEY',
18
+ },
19
+ },
20
+ required: ['placeholder'],
21
+ },
22
+ };
23
+ getZodSchema() {
24
+ return {
25
+ placeholder: z.string().min(1).describe('The placeholder of the secret (environment variable)'),
26
+ };
27
+ }
28
+ async execute(input) {
29
+ try {
30
+ // 验证输入参数
31
+ this.validateInput(input, ['placeholder']);
32
+ const { placeholder } = input;
33
+ console.log(chalk.blue(`\n🔐 准备添加密钥: ${placeholder}...\n`));
34
+ console.log(chalk.green(`\n✓ 密钥 "${placeholder}" 准备就绪\n`));
35
+ // 构建返回的 JSON 数据
36
+ const resultData = {
37
+ placeholder: placeholder,
38
+ secret_added: false,
39
+ secret_names: [placeholder],
40
+ requires_approval: true,
41
+ message: '密钥等待用户提供值',
42
+ instructions: `请在 Supabase 项目设置中添加此密钥,或使用 Supabase CLI: supabase secrets set ${placeholder}=your_value_here`
43
+ };
44
+ return this.successWithContent(JSON.stringify(resultData, null, 2));
45
+ }
46
+ catch (error) {
47
+ const message = error instanceof Error ? error.message : '生成密钥请求失败';
48
+ console.log(chalk.red(`\n✖ ${message}\n`));
49
+ return this.errorWithContent(message);
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=add-secret-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-secret-tool.js","sourceRoot":"","sources":["../../src/tools/add-secret-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,OAAO,aAAc,SAAQ,QAAQ;IACzC,UAAU,GAAmB;QAC3B,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uFAAuF;QACpG,YAAY,EAAE;YACZ,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,4EAA4E;iBAC1F;aACF;YACD,QAAQ,EAAE,CAAC,aAAa,CAAC;SAC1B;KACF,CAAC;IAEF,YAAY;QACV,OAAO;YACL,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,sDAAsD,CAAC;SAChG,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAA0B;QACtC,IAAI,CAAC;YACH,SAAS;YACT,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;YAE3C,MAAM,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;YAE9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,WAAW,OAAO,CAAC,CAAC,CAAC;YAE5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,WAAW,UAAU,CAAC,CAAC,CAAC;YAE3D,gBAAgB;YAChB,MAAM,UAAU,GAAG;gBACjB,WAAW,EAAE,WAAW;gBACxB,YAAY,EAAE,KAAK;gBACnB,YAAY,EAAE,CAAC,WAAW,CAAC;gBAC3B,iBAAiB,EAAE,IAAI;gBACvB,OAAO,EAAE,WAAW;gBACpB,YAAY,EAAE,iEAAiE,WAAW,kBAAkB;aAC7G,CAAC;YAEF,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,OAAO,IAAI,CAAC,CAAC,CAAC;YAC3C,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;CACF"}
@@ -6,5 +6,6 @@ export { SupabaseEnableTool } from './supabase-enable-tool.js';
6
6
  export { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
7
7
  export { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
8
8
  export { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
9
+ export { AddSecretTool } from './add-secret-tool.js';
9
10
  export { ToolRegistry, ToolConfig } from './tool-registry.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
@@ -6,5 +6,6 @@ export { SupabaseEnableTool } from './supabase-enable-tool.js';
6
6
  export { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
7
7
  export { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
8
8
  export { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
9
+ export { AddSecretTool } from './add-secret-tool.js';
9
10
  export { ToolRegistry } from './tool-registry.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAc,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAc,MAAM,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMxD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAgC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAY5C;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIjC;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAI1C;;OAEG;IACH,MAAM,CAAC,iBAAiB,IAAI,cAAc,EAAE;IAI5C;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE;IAIvB;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;;;OAIG;WACU,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAQ5E;;OAEG;IACH,MAAM,CAAC,gBAAgB,IAAI,GAAG,EAAE;CAOjC"}
1
+ {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOxD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAgC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAa5C;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIjC;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAI1C;;OAEG;IACH,MAAM,CAAC,iBAAiB,IAAI,cAAc,EAAE;IAI5C;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE;IAIvB;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;;;OAIG;WACU,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAQ5E;;OAEG;IACH,MAAM,CAAC,gBAAgB,IAAI,GAAG,EAAE;CAOjC"}
@@ -6,6 +6,7 @@ import { SupabaseEnableTool } from './supabase-enable-tool.js';
6
6
  import { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
7
7
  import { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
8
8
  import { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
9
+ import { AddSecretTool } from './add-secret-tool.js';
9
10
  export class ToolRegistry {
10
11
  static tools = new Map();
11
12
  /**
@@ -21,6 +22,7 @@ export class ToolRegistry {
21
22
  this.register(new SupabaseDeployFunctionsTool(config?.gateway));
22
23
  this.register(new SupabaseExecuteSqlTool(config?.gateway));
23
24
  this.register(new AiGatewayEnableTool(config?.gateway));
25
+ this.register(new AddSecretTool(config?.gateway));
24
26
  }
25
27
  /**
26
28
  * 注册工具
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAQlE,MAAM,OAAO,YAAY;IACf,MAAM,CAAC,KAAK,GAAsB,IAAI,GAAG,EAAE,CAAC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAmB;QACnC,WAAW;QACX,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAEnB,cAAc;QACd,eAAe;QACf,IAAI,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,IAAI,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,IAAI,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAU;QACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,MAAM;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAA0B;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB;QACrB,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;;AAGH,qCAAqC;AACrC,YAAY,CAAC,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAQrD,MAAM,OAAO,YAAY;IACf,MAAM,CAAC,KAAK,GAAsB,IAAI,GAAG,EAAE,CAAC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAmB;QACnC,WAAW;QACX,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAEnB,cAAc;QACd,eAAe;QACf,IAAI,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,IAAI,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,IAAI,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAU;QACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,MAAM;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAA0B;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB;QACrB,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;;AAGH,qCAAqC;AACrC,YAAY,CAAC,UAAU,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "needware-cli",
3
- "version": "1.5.22",
3
+ "version": "1.5.25",
4
4
  "description": "一个功能强大的 Node.js 命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,667 @@
1
+ ---
2
+ name: resend-integration
3
+ description: Complete Resend email integration for sending transactional emails, notifications, and alerts. REQUIRES Supabase integration as prerequisite (Resend functions run on Supabase Edge Functions). Use this skill when users mention sending emails, email notifications, transactional emails, or email alerts. 使用此技能处理邮件发送、邮件通知、事务性邮件等功能。必须先集成 Supabase 才能使用此技能。
4
+ ---
5
+
6
+ # Resend Integration
7
+
8
+ ## Before Starting - Integration Check
9
+
10
+ **CRITICAL**: Resend integration requires Supabase Edge Functions. Before proceeding, always check Supabase integration status:
11
+
12
+ **Step 1: Check Supabase Integration**
13
+
14
+ Check if Supabase is already integrated:
15
+ - Look for `src/lib/supabase.ts` file
16
+ - Check `.env` file for Supabase environment variables:
17
+ - `VITE_SUPABASE_PROJECT_ID`
18
+ - `VITE_SUPABASE_PUBLISHABLE_KEY`
19
+ - `VITE_SUPABASE_URL`
20
+ - Check for `supabase/functions/` directory
21
+
22
+ **Step 2: Handle Based on Status**
23
+
24
+ **If Supabase is already integrated** (supabase.ts exists with valid configuration):
25
+ - ✓ Proceed with Resend integration
26
+ - Inform user: "✓ Supabase is integrated. Proceeding with Resend setup."
27
+
28
+ **If Supabase is NOT integrated** (no supabase.ts or missing environment variables):
29
+ - ❌ Stop immediately
30
+ - Inform user: "⚠️ Supabase integration is required before setting up Resend. Resend email functions run on Supabase Edge Functions."
31
+ - Suggest: "Please enable Supabase first by saying 'Enable Cloud' or use the supabase-integration skill."
32
+ - Do NOT proceed with Resend setup until Supabase is properly configured
33
+
34
+ **Step 3: Add Resend API Key Secret**
35
+
36
+ Once Supabase is confirmed integrated:
37
+ 1. Call `add-secret` tool to add the Resend API key to Supabase secrets
38
+ 2. The tool will prompt user to add the `RESEND_API_KEY` secret
39
+ 3. Wait for user confirmation: `needware tool use: Approved`
40
+ 4. After approval, the secret will be added to Supabase Edge Functions environment
41
+ 5. Proceed to next step only after successful secret addition
42
+
43
+
44
+ **Step 4: Verify Edge Functions Support**
45
+
46
+ Once Supabase is confirmed and Resend API key secret is added:
47
+ - Check if `supabase/functions/` directory exists
48
+ - If not, create the directory structure
49
+ - Proceed with Resend Edge Function creation
50
+
51
+ ---
52
+
53
+ ## Quick Start
54
+
55
+ ### When to Use This Skill
56
+
57
+ Use this skill when working with:
58
+ - Transactional emails (welcome emails, password resets)
59
+ - Email notifications (alerts, reminders)
60
+ - Batch email sending
61
+ - HTML email templates
62
+ - Email verification and confirmation
63
+ - User communication
64
+
65
+ **关键原则**:
66
+ - ✅ 使用 Resend 作为邮件发送服务
67
+ - ✅ 所有邮件功能通过 Supabase Edge Functions 实现
68
+ - ⚠️ **必须先集成 Supabase** - Resend 依赖 Supabase Edge Functions 运行
69
+
70
+ ## Initial Setup
71
+
72
+ ### Prerequisites
73
+
74
+ **Required**:
75
+ 1. ✅ **Supabase must be integrated** - Resend functions run on Supabase Edge Functions
76
+ - Verify `src/lib/supabase.ts` exists
77
+ - Verify Supabase environment variables are configured
78
+ - Verify `supabase/functions/` directory exists
79
+
80
+ 2. 🔑 **Resend API key** - If no API key is available, remind user to:
81
+ - Visit [Resend Dashboard](https://resend.com/api-keys)
82
+ - Create a new API key
83
+ - Add key to Supabase Edge Functions environment variables (`supabase/.env.local`)
84
+
85
+ ### Installation in Edge Functions
86
+
87
+ **Step 1: Import Resend in Edge Function**
88
+
89
+ Create a new Edge Function file in `/supabase/functions/<function-name>/index.ts`:
90
+
91
+ ```typescript
92
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
93
+ import { Resend } from "https://esm.sh/resend@2.0.0";
94
+
95
+ const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
96
+ ```
97
+
98
+ **Step 2: Configure Environment Variables**
99
+
100
+ Add to `/supabase/.env.local`:
101
+
102
+ ```env
103
+ RESEND_API_KEY=re_your_api_key_here
104
+ ```
105
+
106
+ **Step 3: CORS Headers Setup**
107
+
108
+ Always include CORS headers for client-side invocation:
109
+
110
+ ```typescript
111
+ const corsHeaders = {
112
+ "Access-Control-Allow-Origin": "*",
113
+ "Access-Control-Allow-Headers":
114
+ "authorization, x-client-info, apikey, content-type",
115
+ };
116
+ ```
117
+
118
+ ## Email Sending Patterns
119
+
120
+ ### Basic Email Template
121
+
122
+ ```typescript
123
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
124
+ import { Resend } from "https://esm.sh/resend@2.0.0";
125
+
126
+ const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
127
+
128
+ const corsHeaders = {
129
+ "Access-Control-Allow-Origin": "*",
130
+ "Access-Control-Allow-Headers":
131
+ "authorization, x-client-info, apikey, content-type",
132
+ };
133
+
134
+ interface EmailRequest {
135
+ to: string;
136
+ subject: string;
137
+ message: string;
138
+ }
139
+
140
+ const handler = async (req: Request): Promise<Response> => {
141
+ // Handle CORS preflight requests
142
+ if (req.method === "OPTIONS") {
143
+ return new Response(null, { headers: corsHeaders });
144
+ }
145
+
146
+ try {
147
+ const { to, subject, message }: EmailRequest = await req.json();
148
+
149
+ const { data, error } = await resend.emails.send({
150
+ from: "Your App <onboarding@resend.dev>",
151
+ to: [to],
152
+ subject: subject,
153
+ html: `<p>${message}</p>`,
154
+ });
155
+
156
+ if (error) {
157
+ throw new Error(error.message);
158
+ }
159
+
160
+ return new Response(
161
+ JSON.stringify({ success: true, data }),
162
+ {
163
+ status: 200,
164
+ headers: { "Content-Type": "application/json", ...corsHeaders },
165
+ }
166
+ );
167
+ } catch (error: any) {
168
+ console.error("Error sending email:", error);
169
+ return new Response(
170
+ JSON.stringify({ error: error.message }),
171
+ {
172
+ status: 500,
173
+ headers: { "Content-Type": "application/json", ...corsHeaders },
174
+ }
175
+ );
176
+ }
177
+ };
178
+
179
+ serve(handler);
180
+ ```
181
+
182
+ ### Batch Email Sending
183
+
184
+ Send emails to multiple recipients:
185
+
186
+ ```typescript
187
+ interface BatchEmailRequest {
188
+ recipients: {
189
+ name: string;
190
+ email: string;
191
+ }[];
192
+ subject: string;
193
+ message: string;
194
+ }
195
+
196
+ const handler = async (req: Request): Promise<Response> => {
197
+ if (req.method === "OPTIONS") {
198
+ return new Response(null, { headers: corsHeaders });
199
+ }
200
+
201
+ try {
202
+ const { recipients, subject, message }: BatchEmailRequest = await req.json();
203
+
204
+ if (!recipients || recipients.length === 0) {
205
+ return new Response(
206
+ JSON.stringify({ error: "No recipients provided" }),
207
+ {
208
+ status: 400,
209
+ headers: { "Content-Type": "application/json", ...corsHeaders },
210
+ }
211
+ );
212
+ }
213
+
214
+ // Send emails in parallel
215
+ const results = await Promise.all(
216
+ recipients.map(async (recipient) => {
217
+ const emailResponse = await resend.emails.send({
218
+ from: "Your App <onboarding@resend.dev>",
219
+ to: [recipient.email],
220
+ subject: subject,
221
+ html: `<p>Hi ${recipient.name},</p><p>${message}</p>`,
222
+ });
223
+ return {
224
+ recipient: recipient.email,
225
+ response: emailResponse
226
+ };
227
+ })
228
+ );
229
+
230
+ return new Response(
231
+ JSON.stringify({ success: true, results }),
232
+ {
233
+ status: 200,
234
+ headers: { "Content-Type": "application/json", ...corsHeaders },
235
+ }
236
+ );
237
+ } catch (error: any) {
238
+ return new Response(
239
+ JSON.stringify({ error: error.message }),
240
+ {
241
+ status: 500,
242
+ headers: { "Content-Type": "application/json", ...corsHeaders },
243
+ }
244
+ );
245
+ }
246
+ };
247
+ ```
248
+
249
+ ### Alert Email Example
250
+
251
+ Complete example for sending alert emails:
252
+
253
+ ```typescript
254
+ import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
255
+ import { Resend } from "https://esm.sh/resend@2.0.0";
256
+
257
+ const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
258
+
259
+ const corsHeaders = {
260
+ "Access-Control-Allow-Origin": "*",
261
+ "Access-Control-Allow-Headers":
262
+ "authorization, x-client-info, apikey, content-type",
263
+ };
264
+
265
+ interface AlertEmailRequest {
266
+ contacts: {
267
+ name: string;
268
+ email: string;
269
+ }[];
270
+ userName?: string;
271
+ message?: string;
272
+ }
273
+
274
+ const handler = async (req: Request): Promise<Response> => {
275
+ if (req.method === "OPTIONS") {
276
+ return new Response(null, { headers: corsHeaders });
277
+ }
278
+
279
+ try {
280
+ const { contacts, userName = "用户", message }: AlertEmailRequest = await req.json();
281
+
282
+ if (!contacts || contacts.length === 0) {
283
+ return new Response(
284
+ JSON.stringify({ error: "No contacts provided" }),
285
+ {
286
+ status: 400,
287
+ headers: { "Content-Type": "application/json", ...corsHeaders },
288
+ }
289
+ );
290
+ }
291
+
292
+ const results = await Promise.all(
293
+ contacts.map(async (contact) => {
294
+ const emailResponse = await resend.emails.send({
295
+ from: "Your App <onboarding@resend.dev>",
296
+ to: [contact.email],
297
+ subject: `⚠️ Alert: ${userName} triggered notification`,
298
+ html: `
299
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
300
+ <h1 style="color: #f59e0b; border-bottom: 2px solid #f59e0b; padding-bottom: 10px;">
301
+ ⚠️ Alert Notification
302
+ </h1>
303
+ <p>Hi ${contact.name},</p>
304
+ <p>This is an alert notification from Your App.</p>
305
+ <p><strong>${userName}</strong> ${message || "has triggered an alert notification."}</p>
306
+ <div style="background: #fef3c7; padding: 15px; border-radius: 8px; margin: 20px 0;">
307
+ <p style="margin: 0; color: #92400e;">
308
+ Please take appropriate action.
309
+ </p>
310
+ </div>
311
+ <p style="color: #666; font-size: 14px;">
312
+ This is an automated email from Your App.
313
+ </p>
314
+ </div>
315
+ `,
316
+ });
317
+ return { contact: contact.email, response: emailResponse };
318
+ })
319
+ );
320
+
321
+ console.log("Emails sent successfully:", results);
322
+
323
+ return new Response(
324
+ JSON.stringify({ success: true, results }),
325
+ {
326
+ status: 200,
327
+ headers: { "Content-Type": "application/json", ...corsHeaders },
328
+ }
329
+ );
330
+ } catch (error: any) {
331
+ console.error("Error in send-alert-email function:", error);
332
+ return new Response(
333
+ JSON.stringify({ error: error.message }),
334
+ {
335
+ status: 500,
336
+ headers: { "Content-Type": "application/json", ...corsHeaders },
337
+ }
338
+ );
339
+ }
340
+ };
341
+
342
+ serve(handler);
343
+ ```
344
+
345
+ ## Email Templates
346
+
347
+ ### Welcome Email Template
348
+
349
+ ```typescript
350
+ const welcomeEmailHTML = (userName: string) => `
351
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
352
+ <h1>Welcome to Our App!</h1>
353
+ <p>Hi ${userName},</p>
354
+ <p>Thank you for signing up. We're excited to have you on board!</p>
355
+ <a href="https://yourapp.com/get-started"
356
+ style="background: #3b82f6; color: white; padding: 12px 24px;
357
+ text-decoration: none; border-radius: 6px; display: inline-block;">
358
+ Get Started
359
+ </a>
360
+ </div>
361
+ `;
362
+ ```
363
+
364
+ ### Password Reset Template
365
+
366
+ ```typescript
367
+ const resetPasswordHTML = (resetLink: string) => `
368
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
369
+ <h1>Reset Your Password</h1>
370
+ <p>Click the button below to reset your password:</p>
371
+ <a href="${resetLink}"
372
+ style="background: #ef4444; color: white; padding: 12px 24px;
373
+ text-decoration: none; border-radius: 6px; display: inline-block;">
374
+ Reset Password
375
+ </a>
376
+ <p style="color: #666; font-size: 14px; margin-top: 20px;">
377
+ If you didn't request this, please ignore this email.
378
+ </p>
379
+ </div>
380
+ `;
381
+ ```
382
+
383
+ ### Notification Email Template
384
+
385
+ ```typescript
386
+ const notificationEmailHTML = (title: string, content: string) => `
387
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
388
+ <h2 style="color: #1f2937; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;">
389
+ ${title}
390
+ </h2>
391
+ <div style="background: #f3f4f6; padding: 15px; border-radius: 8px; margin: 20px 0;">
392
+ <p style="margin: 0; color: #374151;">${content}</p>
393
+ </div>
394
+ <p style="color: #6b7280; font-size: 14px;">
395
+ This is an automated notification from Your App.
396
+ </p>
397
+ </div>
398
+ `;
399
+ ```
400
+
401
+ ## Invoking from Client
402
+
403
+ ### Using Supabase Functions
404
+
405
+ ```typescript
406
+ // In your React/Vue/etc. component
407
+ import { supabase } from '@/lib/supabase';
408
+
409
+ // Send single email
410
+ const sendEmail = async () => {
411
+ const { data, error } = await supabase.functions.invoke('send-email', {
412
+ body: {
413
+ to: 'user@example.com',
414
+ subject: 'Hello from Your App',
415
+ message: 'This is a test email'
416
+ }
417
+ });
418
+
419
+ if (error) {
420
+ console.error('Error sending email:', error);
421
+ return;
422
+ }
423
+
424
+ console.log('Email sent:', data);
425
+ };
426
+
427
+ // Send alert emails
428
+ const sendAlertEmails = async () => {
429
+ const { data, error } = await supabase.functions.invoke('send-alert-email', {
430
+ body: {
431
+ contacts: [
432
+ { name: 'John Doe', email: 'john@example.com' },
433
+ { name: 'Jane Smith', email: 'jane@example.com' }
434
+ ],
435
+ userName: '张三',
436
+ message: '触发了紧急通知'
437
+ }
438
+ });
439
+
440
+ if (error) {
441
+ console.error('Error sending alerts:', error);
442
+ return;
443
+ }
444
+
445
+ console.log('Alerts sent:', data);
446
+ };
447
+ ```
448
+
449
+ ## Common Use Cases
450
+
451
+ ### 1. Welcome Emails
452
+ Send when user signs up:
453
+ ```typescript
454
+ const { data, error } = await supabase.functions.invoke('send-welcome-email', {
455
+ body: { email: user.email, name: user.name }
456
+ });
457
+ ```
458
+
459
+ ### 2. Password Reset
460
+ Send password reset link:
461
+ ```typescript
462
+ const { data, error } = await supabase.functions.invoke('send-password-reset', {
463
+ body: { email: user.email, resetToken: token }
464
+ });
465
+ ```
466
+
467
+ ### 3. Email Verification
468
+ Send verification code:
469
+ ```typescript
470
+ const { data, error } = await supabase.functions.invoke('send-verification', {
471
+ body: { email: user.email, code: verificationCode }
472
+ });
473
+ ```
474
+
475
+ ### 4. Transaction Confirmations
476
+ Send order/payment confirmations:
477
+ ```typescript
478
+ const { data, error } = await supabase.functions.invoke('send-confirmation', {
479
+ body: { email: user.email, orderDetails: order }
480
+ });
481
+ ```
482
+
483
+ ### 5. Scheduled Reminders
484
+ Send reminder emails:
485
+ ```typescript
486
+ const { data, error } = await supabase.functions.invoke('send-reminder', {
487
+ body: { email: user.email, reminderText: reminder }
488
+ });
489
+ ```
490
+
491
+ ## Best Practices
492
+
493
+ ### 1. Email Sender Configuration
494
+
495
+ **✅ DO**:
496
+ - Use custom domain for production: `Your App <noreply@yourdomain.com>`
497
+ - Use descriptive sender names
498
+ - Keep sender email consistent
499
+
500
+ **❌ DON'T**:
501
+ - Use `onboarding@resend.dev` in production (for testing only)
502
+ - Change sender email frequently
503
+ - Use confusing sender names
504
+
505
+ ### 2. Error Handling
506
+
507
+ Always handle errors gracefully:
508
+
509
+ ```typescript
510
+ try {
511
+ const { data, error } = await resend.emails.send({...});
512
+
513
+ if (error) {
514
+ console.error("Resend API error:", error);
515
+ throw new Error(error.message);
516
+ }
517
+
518
+ return new Response(JSON.stringify({ success: true, data }), {...});
519
+ } catch (error: any) {
520
+ console.error("Unexpected error:", error);
521
+ return new Response(
522
+ JSON.stringify({ error: error.message }),
523
+ { status: 500, headers: {...} }
524
+ );
525
+ }
526
+ ```
527
+
528
+ ### 3. Rate Limiting
529
+
530
+ Implement rate limiting for batch sends:
531
+
532
+ ```typescript
533
+ // Send emails in batches of 10
534
+ const batchSize = 10;
535
+ for (let i = 0; i < recipients.length; i += batchSize) {
536
+ const batch = recipients.slice(i, i + batchSize);
537
+ await Promise.all(batch.map(recipient => sendEmail(recipient)));
538
+ // Add delay between batches if needed
539
+ await new Promise(resolve => setTimeout(resolve, 1000));
540
+ }
541
+ ```
542
+
543
+ ### 4. HTML Email Best Practices
544
+
545
+ **✅ DO**:
546
+ - Use inline CSS styles
547
+ - Keep width under 600px
548
+ - Test on multiple email clients
549
+ - Include plain text fallback
550
+ - Use responsive design
551
+ - Add unsubscribe links (if applicable)
552
+
553
+ **❌ DON'T**:
554
+ - Use external CSS files
555
+ - Use JavaScript
556
+ - Use complex layouts
557
+ - Forget alt text for images
558
+
559
+ ### 5. Security
560
+
561
+ **✅ DO**:
562
+ - Store API key in environment variables
563
+ - Validate input data
564
+ - Sanitize user-generated content
565
+ - Use HTTPS for all links
566
+ - Implement rate limiting
567
+
568
+ **❌ DON'T**:
569
+ - Hardcode API keys
570
+ - Trust user input without validation
571
+ - Include sensitive data in emails
572
+ - Allow arbitrary email sending
573
+
574
+ ## Debugging
575
+
576
+ ### Common Issues
577
+
578
+ **1. Email not received**:
579
+ - Check spam/junk folder
580
+ - Verify recipient email address
581
+ - Check Resend Dashboard for delivery status
582
+ - Verify sender domain configuration
583
+
584
+ **2. API Key errors**:
585
+ - Verify `RESEND_API_KEY` in environment variables
586
+ - Check API key is active in Resend Dashboard
587
+ - Ensure proper Deno.env.get() usage
588
+
589
+ **3. CORS errors**:
590
+ - Verify CORS headers are included
591
+ - Handle OPTIONS requests
592
+ - Check browser console for specific errors
593
+
594
+ **4. Function timeout**:
595
+ - Reduce batch size
596
+ - Optimize email sending logic
597
+ - Check for blocking operations
598
+
599
+ ### Logging
600
+
601
+ Add comprehensive logging:
602
+
603
+ ```typescript
604
+ console.log("Sending email to:", recipient.email);
605
+ console.log("Email response:", emailResponse);
606
+ console.error("Error sending email:", error);
607
+ ```
608
+
609
+ ### Testing
610
+
611
+ Test Edge Function locally:
612
+
613
+ ```bash
614
+ supabase functions serve send-email --env-file supabase/.env.local
615
+ ```
616
+
617
+ Test with curl:
618
+
619
+ ```bash
620
+ curl -i --location --request POST 'http://localhost:54321/functions/v1/send-email' \
621
+ --header 'Authorization: Bearer YOUR_ANON_KEY' \
622
+ --header 'Content-Type: application/json' \
623
+ --data '{"to":"test@example.com","subject":"Test","message":"Hello"}'
624
+ ```
625
+
626
+ ## Project Structure
627
+
628
+ ```
629
+ project-root/
630
+ ├── supabase/
631
+ │ ├── functions/
632
+ │ │ ├── send-email/
633
+ │ │ │ └── index.ts # Basic email sending
634
+ │ │ ├── send-alert-email/
635
+ │ │ │ └── index.ts # Alert emails
636
+ │ │ ├── send-welcome-email/
637
+ │ │ │ └── index.ts # Welcome emails
638
+ │ │ └── send-notification/
639
+ │ │ └── index.ts # Notification emails
640
+ │ └── .env.local # RESEND_API_KEY
641
+ ├── src/
642
+ │ ├── lib/
643
+ │ │ └── supabase.ts # Supabase Client
644
+ │ └── components/
645
+ │ └── EmailForm.tsx # Email sending UI
646
+ └── .env # Frontend env vars
647
+ ```
648
+
649
+ ## Additional Resources
650
+
651
+ - [Resend Documentation](https://resend.com/docs)
652
+ - [Resend API Reference](https://resend.com/docs/api-reference)
653
+ - [Email Templates Best Practices](https://resend.com/docs/send-with-react)
654
+ - [Supabase Edge Functions Guide](https://supabase.com/docs/guides/functions)
655
+
656
+ ## Migration Checklist
657
+
658
+ When moving to production:
659
+
660
+ - [ ] Register custom domain in Resend Dashboard
661
+ - [ ] Update sender email from `onboarding@resend.dev` to custom domain
662
+ - [ ] Configure SPF, DKIM, and DMARC records
663
+ - [ ] Update `RESEND_API_KEY` with production key
664
+ - [ ] Test all email flows
665
+ - [ ] Set up email delivery monitoring
666
+ - [ ] Implement unsubscribe functionality (if needed)
667
+ - [ ] Add email preference management