@polymorphism-tech/morph-spec 6.0.0 → 7.1.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 (114) hide show
  1. package/README.md +367 -366
  2. package/docs/CHEATSHEET.md +3 -3
  3. package/docs/QUICKSTART.md +4 -4
  4. package/framework/CLAUDE.md +13 -0
  5. package/framework/MORPH.md +57 -36
  6. package/framework/agents.json +132 -127
  7. package/framework/commands/morph-apply.md +4 -21
  8. package/framework/commands/morph-archive.md +1 -1
  9. package/framework/commands/morph-proposal.md +2 -0
  10. package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
  11. package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
  12. package/framework/hooks/claude-code/core/state-sync.js +212 -0
  13. package/framework/hooks/shared/phase-utils.js +5 -4
  14. package/framework/hooks/shared/state-reader.js +1 -1
  15. package/framework/phases.json +1 -1
  16. package/framework/rubrics/backend.json +41 -36
  17. package/framework/rules/ai-agents-standards.md +45 -15
  18. package/framework/rules/morph-workflow.md +15 -34
  19. package/framework/rules/neon-standards.md +25 -26
  20. package/framework/schemas/tasks.schema.json +35 -0
  21. package/framework/skills/core/morph-apply/SKILL.md +154 -0
  22. package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
  23. package/framework/skills/core/morph-implement/SKILL.md +25 -14
  24. package/framework/skills/core/morph-plan/SKILL.md +91 -23
  25. package/framework/skills/core/morph-proposal/SKILL.md +137 -0
  26. package/framework/skills/core/morph-review/SKILL.md +43 -7
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
  28. package/framework/standards/STANDARDS.json +1729 -1134
  29. package/framework/standards/ai-agents/agent-session.md +160 -0
  30. package/framework/standards/ai-agents/batching.md +193 -0
  31. package/framework/standards/ai-agents/context-providers.md +188 -0
  32. package/framework/standards/ai-agents/mcp-server.md +250 -0
  33. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  34. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  35. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  36. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  37. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  38. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  39. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  40. package/framework/standards/ai-agents/production.md +114 -258
  41. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  42. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  43. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  44. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  45. package/framework/standards/ai-agents/setup.md +142 -316
  46. package/framework/standards/ai-agents/structured-output.md +249 -0
  47. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  48. package/framework/standards/ai-agents/workflows.md +114 -284
  49. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  50. package/framework/standards/backend/api/validation.md +92 -88
  51. package/framework/standards/backend/database/postgresql/database.md +336 -332
  52. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  53. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  54. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  55. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  56. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  57. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  58. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  59. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  60. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  61. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  62. package/framework/standards/data/nosql/cache/redis.md +106 -97
  63. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  64. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  65. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  66. package/framework/standards/frontend/design-system/animations.md +164 -160
  67. package/framework/standards/frontend/design-system/naming.md +68 -64
  68. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  69. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  70. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  71. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  72. package/framework/standards/frontend/nextjs/components.md +136 -132
  73. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  74. package/framework/standards/frontend/nextjs/forms.md +134 -128
  75. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  76. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  77. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  78. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  79. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  80. package/framework/standards/frontend/nextjs/testing.md +115 -111
  81. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  82. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  83. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  84. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  85. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  86. package/framework/templates/REGISTRY.json +1745 -1239
  87. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  88. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  89. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  90. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  91. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  92. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  93. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  96. package/framework/templates/docs/onboarding.md +4 -4
  97. package/framework/templates/tasks.template.json +17 -0
  98. package/package.json +89 -83
  99. package/src/commands/project/init.js +16 -0
  100. package/src/commands/validation/validate-feature.js +1 -1
  101. package/src/core/paths/output-schema.js +6 -6
  102. package/src/core/state/state-manager.js +1 -1
  103. package/src/core/templates/template-registry.js +289 -256
  104. package/src/lib/detectors/design-system-detector.js +1 -1
  105. package/src/lib/generators/context-generator.js +516 -518
  106. package/src/lib/generators/scaffold-generator.js +23 -26
  107. package/src/lib/installers/memory-installer.js +138 -0
  108. package/src/lib/phase-chain/phase-validator.js +4 -4
  109. package/src/lib/risk-detector.js +80 -0
  110. package/src/lib/tasks/tasks-json-parser.js +185 -0
  111. package/src/scripts/setup-infra.js +25 -0
  112. package/src/utils/hooks-installer.js +59 -10
  113. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  114. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,385 +1,400 @@
1
- # Resend Email Service Integration Standard
2
-
3
- > **Scope:** universal
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** resend, email, transactional, smtp, sendgrid alternative
6
- > **Load When:** resend or transactional email keywords detected
7
-
8
- Modern transactional email service for developers, built for Next.js and .NET.
9
-
10
- ---
11
-
12
- ## Overview
13
-
14
- Resend provides:
15
- - Simple API for sending emails
16
- - React Email template support
17
- - Email tracking and analytics
18
- - Domain verification
19
- - Webhooks for delivery status
20
- - 100 free emails/day
21
-
22
- **Stack:** Universal (.NET + Next.js)
23
-
24
- ---
25
-
26
- ## Core Principles
27
-
28
- 1. **API-First**: Use REST API, not SMTP
29
- 2. **Type Safety**: Use official SDKs with TypeScript/C# types
30
- 3. **Template-Based**: Use React Email for Next.js, Razor for .NET
31
- 4. **Async**: Always send emails asynchronously
32
- 5. **Monitor**: Track delivery status via webhooks
33
-
34
- ---
35
-
36
- ## Installation & Setup
37
-
38
- ### Next.js Setup
39
-
40
- ```bash
41
- npm install resend react-email
42
- ```
43
-
44
- ```typescript
45
- // lib/resend.ts
46
- import { Resend } from 'resend';
47
-
48
- export const resend = new Resend(process.env.RESEND_API_KEY);
49
- ```
50
-
51
- ### .NET Setup
52
-
53
- ```bash
54
- dotnet add package Resend
55
- ```
56
-
57
- ```csharp
58
- // Program.cs
59
- using Resend;
60
-
61
- builder.Services.AddResend(options =>
62
- {
63
- options.ApiKey = builder.Configuration["Resend:ApiKey"]!;
64
- });
65
- ```
66
-
67
- ### Environment Variables
68
-
69
- ```bash
70
- # .env.local (Next.js) or appsettings.json (.NET)
71
- RESEND_API_KEY=re_...
72
- RESEND_FROM_EMAIL=noreply@yourdomain.com
73
- ```
74
-
75
- ---
76
-
77
- ## Next.js Usage
78
-
79
- ### React Email Template
80
-
81
- ```tsx
82
- // emails/WelcomeEmail.tsx
83
- import { Html, Button, Container, Text } from '@react-email/components';
84
-
85
- interface WelcomeEmailProps {
86
- name: string;
87
- verificationUrl: string;
88
- }
89
-
90
- export default function WelcomeEmail({ name, verificationUrl }: WelcomeEmailProps) {
91
- return (
92
- <Html>
93
- <Container>
94
- <Text>Welcome, {name}!</Text>
95
- <Button href={verificationUrl}>Verify Email</Button>
96
- </Container>
97
- </Html>
98
- );
99
- }
100
- ```
101
-
102
- ### Send Email (API Route)
103
-
104
- ```typescript
105
- // app/api/send-welcome/route.ts
106
- import { resend } from '@/lib/resend';
107
- import WelcomeEmail from '@/emails/WelcomeEmail';
108
-
109
- export async function POST(req: Request) {
110
- const { email, name, verificationUrl } = await req.json();
111
-
112
- try {
113
- const { data, error } = await resend.emails.send({
114
- from: 'MyApp <noreply@yourdomain.com>',
115
- to: email,
116
- subject: 'Welcome to MyApp!',
117
- react: WelcomeEmail({ name, verificationUrl }),
118
- });
119
-
120
- if (error) {
121
- return Response.json({ error }, { status: 500 });
122
- }
123
-
124
- return Response.json({ id: data.id });
125
- } catch (error) {
126
- return Response.json({ error: 'Failed to send email' }, { status: 500 });
127
- }
128
- }
129
- ```
130
-
131
- ---
132
-
133
- ## .NET Usage
134
-
135
- ### Email Service Interface
136
-
137
- ```csharp
138
- // Services/IEmailService.cs
139
- public interface IEmailService
140
- {
141
- Task<string> SendWelcomeEmailAsync(string toEmail, string name);
142
- Task<string> SendPasswordResetAsync(string toEmail, string resetUrl);
143
- }
144
- ```
145
-
146
- ### Email Service Implementation
147
-
148
- ```csharp
149
- // Services/EmailService.cs
150
- using Resend;
151
-
152
- public class EmailService : IEmailService
153
- {
154
- private readonly IResend _resend;
155
- private readonly IConfiguration _config;
156
-
157
- public EmailService(IResend resend, IConfiguration config)
158
- {
159
- _resend = resend;
160
- _config = config;
161
- }
162
-
163
- public async Task<string> SendWelcomeEmailAsync(string toEmail, string name)
164
- {
165
- var message = new EmailMessage
166
- {
167
- From = _config["Resend:FromEmail"]!,
168
- To = toEmail,
169
- Subject = "Welcome!",
170
- HtmlBody = $@"
171
- <h1>Welcome, {name}!</h1>
172
- <p>Thank you for joining us.</p>
173
- "
174
- };
175
-
176
- var response = await _resend.EmailSendAsync(message);
177
- return response.Id;
178
- }
179
-
180
- public async Task<string> SendPasswordResetAsync(string toEmail, string resetUrl)
181
- {
182
- var message = new EmailMessage
183
- {
184
- From = _config["Resend:FromEmail"]!,
185
- To = toEmail,
186
- Subject = "Reset Your Password",
187
- HtmlBody = $@"
188
- <h1>Password Reset</h1>
189
- <p>Click the link below to reset your password:</p>
190
- <a href=""{resetUrl}"">Reset Password</a>
191
- "
192
- };
193
-
194
- var response = await _resend.EmailSendAsync(message);
195
- return response.Id;
196
- }
197
- }
198
-
199
- // Register in DI
200
- builder.Services.AddScoped<IEmailService, EmailService>();
201
- ```
202
-
203
- ---
204
-
205
- ## Batch Emails
206
-
207
- ### Next.js Batch Send
208
-
209
- ```typescript
210
- // Send to multiple recipients
211
- const { data, error } = await resend.batch.send([
212
- {
213
- from: 'MyApp <noreply@yourdomain.com>',
214
- to: 'user1@example.com',
215
- subject: 'Notification',
216
- react: NotificationEmail({ message: 'Update 1' }),
217
- },
218
- {
219
- from: 'MyApp <noreply@yourdomain.com>',
220
- to: 'user2@example.com',
221
- subject: 'Notification',
222
- react: NotificationEmail({ message: 'Update 2' }),
223
- },
224
- ]);
225
- ```
226
-
227
- ---
228
-
229
- ## Webhooks for Delivery Status
230
-
231
- ### Setup Webhook Endpoint
232
-
233
- ```typescript
234
- // app/api/webhooks/resend/route.ts
235
- import { headers } from 'next/headers';
236
- import { createHmac } from 'crypto';
237
-
238
- export async function POST(req: Request) {
239
- const body = await req.text();
240
- const signature = headers().get('resend-signature');
241
-
242
- // Verify webhook signature
243
- const secret = process.env.RESEND_WEBHOOK_SECRET!;
244
- const expectedSignature = createHmac('sha256', secret)
245
- .update(body)
246
- .digest('hex');
247
-
248
- if (signature !== expectedSignature) {
249
- return new Response('Invalid signature', { status: 401 });
250
- }
251
-
252
- const event = JSON.parse(body);
253
-
254
- switch (event.type) {
255
- case 'email.delivered':
256
- await handleEmailDelivered(event.data);
257
- break;
258
- case 'email.bounced':
259
- await handleEmailBounced(event.data);
260
- break;
261
- case 'email.complained':
262
- await handleEmailComplained(event.data);
263
- break;
264
- }
265
-
266
- return new Response('OK');
267
- }
268
- ```
269
-
270
- ---
271
-
272
- ## Best Practices
273
-
274
- ### Background Jobs (with Hangfire)
275
-
276
- ```csharp
277
- // Don't block HTTP requests with email sending
278
- BackgroundJob.Enqueue<IEmailService>(
279
- x => x.SendWelcomeEmailAsync(email, name));
280
- ```
281
-
282
- ### Rate Limiting
283
-
284
- ```typescript
285
- // Implement rate limiting for email sends
286
- import { Ratelimit } from '@upstash/ratelimit';
287
- import { Redis } from '@upstash/redis';
288
-
289
- const ratelimit = new Ratelimit({
290
- redis: Redis.fromEnv(),
291
- limiter: Ratelimit.slidingWindow(10, '1 h'), // 10 emails per hour
292
- });
293
-
294
- const { success } = await ratelimit.limit(userId);
295
- if (!success) {
296
- return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
297
- }
298
-
299
- await resend.emails.send({ ... });
300
- ```
301
-
302
- ### Retry Logic (.NET)
303
-
304
- ```csharp
305
- using Polly;
306
-
307
- var retryPolicy = Policy
308
- .Handle<HttpRequestException>()
309
- .WaitAndRetryAsync(3, retryAttempt =>
310
- TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
311
-
312
- await retryPolicy.ExecuteAsync(async () =>
313
- {
314
- await _resend.EmailSendAsync(message);
315
- });
316
- ```
317
-
318
- ---
319
-
320
- ## Domain Verification
321
-
322
- 1. Go to Resend Dashboard → Domains
323
- 2. Add your domain (e.g., `yourdomain.com`)
324
- 3. Add DNS records (SPF, DKIM, DMARC)
325
- 4. Wait for verification (up to 72 hours)
326
-
327
- ### DNS Records Example
328
-
329
- ```
330
- TXT @ v=spf1 include:resend.com ~all
331
- TXT resend._domainkey <DKIM_VALUE>
332
- TXT _dmarc v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com
333
- ```
334
-
335
- ---
336
-
337
- ## Common Email Templates
338
-
339
- ### Order Confirmation
340
-
341
- ```tsx
342
- export default function OrderConfirmation({ orderNumber, total, items }) {
343
- return (
344
- <Html>
345
- <Container>
346
- <Text>Order #{orderNumber} Confirmed</Text>
347
- <Text>Total: ${total}</Text>
348
- <ul>
349
- {items.map(item => (
350
- <li key={item.id}>{item.name} - ${item.price}</li>
351
- ))}
352
- </ul>
353
- </Container>
354
- </Html>
355
- );
356
- }
357
- ```
358
-
359
- ### Magic Link Auth
360
-
361
- ```tsx
362
- export default function MagicLink({ url, expiresIn }) {
363
- return (
364
- <Html>
365
- <Container>
366
- <Text>Sign in to your account</Text>
367
- <Button href={url}>Sign In</Button>
368
- <Text>This link expires in {expiresIn} minutes.</Text>
369
- </Container>
370
- </Html>
371
- );
372
- }
373
- ```
374
-
375
- ---
376
-
377
- ## References
378
-
379
- - [Resend Documentation](https://resend.com/docs)
380
- - [React Email](https://react.email/)
381
- - [Resend .NET SDK](https://github.com/resend/resend-dotnet)
382
-
383
- ---
384
-
385
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Resend Email Service Integration Standard
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** resend, email, transactional, smtp, sendgrid alternative
6
+ > **Load When:** resend or transactional email keywords detected
7
+
8
+ **Verified against:** Resend Node SDK 4 + Resend .NET SDK + React Email. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Modern transactional email service for developers, built for Next.js and .NET.
13
+
14
+ ---
15
+
16
+ ## Overview
17
+
18
+ Resend provides:
19
+ - Simple API for sending emails
20
+ - React Email template support
21
+ - Email tracking and analytics
22
+ - Domain verification
23
+ - Webhooks for delivery status
24
+ - 100 free emails/day
25
+
26
+ **Stack:** Universal (.NET + Next.js)
27
+
28
+ ---
29
+
30
+ ## Core Principles
31
+
32
+ 1. **API-First**: Use REST API, not SMTP
33
+ 2. **Type Safety**: Use official SDKs with TypeScript/C# types
34
+ 3. **Template-Based**: Use React Email for Next.js, Razor for .NET
35
+ 4. **Async**: Always send emails asynchronously
36
+ 5. **Monitor**: Track delivery status via webhooks
37
+
38
+ ---
39
+
40
+ ## Installation & Setup
41
+
42
+ ### Next.js Setup
43
+
44
+ ```bash
45
+ npm install resend react-email
46
+ ```
47
+
48
+ ```typescript
49
+ // lib/resend.ts
50
+ import { Resend } from 'resend';
51
+
52
+ export const resend = new Resend(process.env.RESEND_API_KEY);
53
+ ```
54
+
55
+ ### .NET Setup
56
+
57
+ ```bash
58
+ dotnet add package Resend
59
+ ```
60
+
61
+ ```csharp
62
+ // Program.cs
63
+ using Resend;
64
+
65
+ builder.Services.AddOptions();
66
+ builder.Services.AddHttpClient<ResendClient>();
67
+ builder.Services.Configure<ResendClientOptions>(o =>
68
+ {
69
+ o.ApiToken = builder.Configuration["Resend:ApiKey"]!;
70
+ });
71
+ builder.Services.AddTransient<IResend, ResendClient>();
72
+ ```
73
+
74
+ ### Environment Variables
75
+
76
+ ```bash
77
+ # .env.local (Next.js) or appsettings.json (.NET)
78
+ RESEND_API_KEY=re_...
79
+ RESEND_FROM_EMAIL=noreply@yourdomain.com
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Next.js Usage
85
+
86
+ ### React Email Template
87
+
88
+ ```tsx
89
+ // emails/WelcomeEmail.tsx
90
+ import { Html, Button, Container, Text } from '@react-email/components';
91
+
92
+ interface WelcomeEmailProps {
93
+ name: string;
94
+ verificationUrl: string;
95
+ }
96
+
97
+ export default function WelcomeEmail({ name, verificationUrl }: WelcomeEmailProps) {
98
+ return (
99
+ <Html>
100
+ <Container>
101
+ <Text>Welcome, {name}!</Text>
102
+ <Button href={verificationUrl}>Verify Email</Button>
103
+ </Container>
104
+ </Html>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ### Send Email (API Route)
110
+
111
+ ```typescript
112
+ // app/api/send-welcome/route.ts
113
+ import { resend } from '@/lib/resend';
114
+ import WelcomeEmail from '@/emails/WelcomeEmail';
115
+
116
+ export async function POST(req: Request) {
117
+ const { email, name, verificationUrl } = await req.json();
118
+
119
+ try {
120
+ const { data, error } = await resend.emails.send({
121
+ from: 'MyApp <noreply@yourdomain.com>',
122
+ to: email,
123
+ subject: 'Welcome to MyApp!',
124
+ react: WelcomeEmail({ name, verificationUrl }),
125
+ });
126
+
127
+ if (error) {
128
+ return Response.json({ error }, { status: 500 });
129
+ }
130
+
131
+ return Response.json({ id: data.id });
132
+ } catch (error) {
133
+ return Response.json({ error: 'Failed to send email' }, { status: 500 });
134
+ }
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## .NET Usage
141
+
142
+ ### Email Service Interface
143
+
144
+ ```csharp
145
+ // Services/IEmailService.cs
146
+ public interface IEmailService
147
+ {
148
+ Task<string> SendWelcomeEmailAsync(string toEmail, string name);
149
+ Task<string> SendPasswordResetAsync(string toEmail, string resetUrl);
150
+ }
151
+ ```
152
+
153
+ ### Email Service Implementation
154
+
155
+ ```csharp
156
+ // Services/EmailService.cs
157
+ using Resend;
158
+
159
+ public class EmailService : IEmailService
160
+ {
161
+ private readonly IResend _resend;
162
+ private readonly IConfiguration _config;
163
+
164
+ public EmailService(IResend resend, IConfiguration config)
165
+ {
166
+ _resend = resend;
167
+ _config = config;
168
+ }
169
+
170
+ public async Task<string> SendWelcomeEmailAsync(string toEmail, string name)
171
+ {
172
+ var message = new EmailMessage
173
+ {
174
+ From = _config["Resend:FromEmail"]!,
175
+ To = toEmail,
176
+ Subject = "Welcome!",
177
+ HtmlBody = $@"
178
+ <h1>Welcome, {name}!</h1>
179
+ <p>Thank you for joining us.</p>
180
+ "
181
+ };
182
+
183
+ var response = await _resend.EmailSendAsync(message);
184
+ return response.Id;
185
+ }
186
+
187
+ public async Task<string> SendPasswordResetAsync(string toEmail, string resetUrl)
188
+ {
189
+ var message = new EmailMessage
190
+ {
191
+ From = _config["Resend:FromEmail"]!,
192
+ To = toEmail,
193
+ Subject = "Reset Your Password",
194
+ HtmlBody = $@"
195
+ <h1>Password Reset</h1>
196
+ <p>Click the link below to reset your password:</p>
197
+ <a href=""{resetUrl}"">Reset Password</a>
198
+ "
199
+ };
200
+
201
+ var response = await _resend.EmailSendAsync(message);
202
+ return response.Id;
203
+ }
204
+ }
205
+
206
+ // Register in DI
207
+ builder.Services.AddScoped<IEmailService, EmailService>();
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Batch Emails
213
+
214
+ ### Next.js Batch Send
215
+
216
+ ```typescript
217
+ // Send to multiple recipients
218
+ const { data, error } = await resend.batch.send([
219
+ {
220
+ from: 'MyApp <noreply@yourdomain.com>',
221
+ to: 'user1@example.com',
222
+ subject: 'Notification',
223
+ react: NotificationEmail({ message: 'Update 1' }),
224
+ },
225
+ {
226
+ from: 'MyApp <noreply@yourdomain.com>',
227
+ to: 'user2@example.com',
228
+ subject: 'Notification',
229
+ react: NotificationEmail({ message: 'Update 2' }),
230
+ },
231
+ ]);
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Webhooks for Delivery Status
237
+
238
+ ### Setup Webhook Endpoint
239
+
240
+ Resend webhooks are signed with **Svix** — verify with `resend.webhooks.verify()`,
241
+ which checks the `svix-id` / `svix-timestamp` / `svix-signature` headers against
242
+ the raw request body. Do **not** roll a manual HMAC: Resend does not use a plain
243
+ `resend-signature` header, and a hand-written check will reject valid webhooks.
244
+
245
+ ```typescript
246
+ // app/api/webhooks/resend/route.ts
247
+ import { resend } from '@/lib/resend';
248
+
249
+ export async function POST(req: Request) {
250
+ // Raw body is required — verification fails on a re-serialized payload
251
+ const payload = await req.text();
252
+
253
+ let event;
254
+ try {
255
+ // Throws on an invalid signature; returns the parsed payload on success
256
+ event = resend.webhooks.verify({
257
+ payload,
258
+ headers: {
259
+ id: req.headers.get('svix-id')!,
260
+ timestamp: req.headers.get('svix-timestamp')!,
261
+ signature: req.headers.get('svix-signature')!,
262
+ },
263
+ webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
264
+ });
265
+ } catch {
266
+ return new Response('Invalid signature', { status: 401 });
267
+ }
268
+
269
+ switch (event.type) {
270
+ case 'email.delivered':
271
+ await handleEmailDelivered(event.data);
272
+ break;
273
+ case 'email.bounced':
274
+ await handleEmailBounced(event.data);
275
+ break;
276
+ case 'email.complained':
277
+ await handleEmailComplained(event.data);
278
+ break;
279
+ }
280
+
281
+ return new Response('OK');
282
+ }
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Best Practices
288
+
289
+ ### Background Jobs (with Hangfire)
290
+
291
+ ```csharp
292
+ // Don't block HTTP requests with email sending
293
+ BackgroundJob.Enqueue<IEmailService>(
294
+ x => x.SendWelcomeEmailAsync(email, name));
295
+ ```
296
+
297
+ ### Rate Limiting
298
+
299
+ ```typescript
300
+ // Implement rate limiting for email sends
301
+ import { Ratelimit } from '@upstash/ratelimit';
302
+ import { Redis } from '@upstash/redis';
303
+
304
+ const ratelimit = new Ratelimit({
305
+ redis: Redis.fromEnv(),
306
+ limiter: Ratelimit.slidingWindow(10, '1 h'), // 10 emails per hour
307
+ });
308
+
309
+ const { success } = await ratelimit.limit(userId);
310
+ if (!success) {
311
+ return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
312
+ }
313
+
314
+ await resend.emails.send({ ... });
315
+ ```
316
+
317
+ ### Retry Logic (.NET)
318
+
319
+ ```csharp
320
+ using Polly;
321
+
322
+ var retryPolicy = Policy
323
+ .Handle<HttpRequestException>()
324
+ .WaitAndRetryAsync(3, retryAttempt =>
325
+ TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
326
+
327
+ await retryPolicy.ExecuteAsync(async () =>
328
+ {
329
+ await _resend.EmailSendAsync(message);
330
+ });
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Domain Verification
336
+
337
+ 1. Go to Resend Dashboard → Domains
338
+ 2. Add your domain (e.g., `yourdomain.com`)
339
+ 3. Add DNS records (SPF, DKIM, DMARC)
340
+ 4. Wait for verification (up to 72 hours)
341
+
342
+ ### DNS Records Example
343
+
344
+ ```
345
+ TXT @ v=spf1 include:resend.com ~all
346
+ TXT resend._domainkey <DKIM_VALUE>
347
+ TXT _dmarc v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Common Email Templates
353
+
354
+ ### Order Confirmation
355
+
356
+ ```tsx
357
+ export default function OrderConfirmation({ orderNumber, total, items }) {
358
+ return (
359
+ <Html>
360
+ <Container>
361
+ <Text>Order #{orderNumber} Confirmed</Text>
362
+ <Text>Total: ${total}</Text>
363
+ <ul>
364
+ {items.map(item => (
365
+ <li key={item.id}>{item.name} - ${item.price}</li>
366
+ ))}
367
+ </ul>
368
+ </Container>
369
+ </Html>
370
+ );
371
+ }
372
+ ```
373
+
374
+ ### Magic Link Auth
375
+
376
+ ```tsx
377
+ export default function MagicLink({ url, expiresIn }) {
378
+ return (
379
+ <Html>
380
+ <Container>
381
+ <Text>Sign in to your account</Text>
382
+ <Button href={url}>Sign In</Button>
383
+ <Text>This link expires in {expiresIn} minutes.</Text>
384
+ </Container>
385
+ </Html>
386
+ );
387
+ }
388
+ ```
389
+
390
+ ---
391
+
392
+ ## References
393
+
394
+ - [Resend Documentation](https://resend.com/docs)
395
+ - [React Email](https://react.email/)
396
+ - [Resend .NET SDK](https://github.com/resend/resend-dotnet)
397
+
398
+ ---
399
+
400
+ *MORPH-SPEC by Polymorphism Tech*