# Resend Email Service Integration Standard

> **Scope:** universal
> **Layer:** 2 (on keyword)
> **Keywords:** resend, email, transactional, smtp, sendgrid alternative
> **Load When:** resend or transactional email keywords detected

**Verified against:** Resend Node SDK 4 + Resend .NET SDK + React Email. Last-verified: 2026-05-20.

---

Modern transactional email service for developers, built for Next.js and .NET.

---

## Overview

Resend provides:
- Simple API for sending emails
- React Email template support
- Email tracking and analytics
- Domain verification
- Webhooks for delivery status
- 100 free emails/day

**Stack:** Universal (.NET + Next.js)

---

## Core Principles

1. **API-First**: Use REST API, not SMTP
2. **Type Safety**: Use official SDKs with TypeScript/C# types
3. **Template-Based**: Use React Email for Next.js, Razor for .NET
4. **Async**: Always send emails asynchronously
5. **Monitor**: Track delivery status via webhooks

---

## Installation & Setup

### Next.js Setup

```bash
npm install resend react-email
```

```typescript
// lib/resend.ts
import { Resend } from 'resend';

export const resend = new Resend(process.env.RESEND_API_KEY);
```

### .NET Setup

```bash
dotnet add package Resend
```

```csharp
// Program.cs
using Resend;

builder.Services.AddOptions();
builder.Services.AddHttpClient<ResendClient>();
builder.Services.Configure<ResendClientOptions>(o =>
{
    o.ApiToken = builder.Configuration["Resend:ApiKey"]!;
});
builder.Services.AddTransient<IResend, ResendClient>();
```

### Environment Variables

```bash
# .env.local (Next.js) or appsettings.json (.NET)
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=noreply@yourdomain.com
```

---

## Next.js Usage

### React Email Template

```tsx
// emails/WelcomeEmail.tsx
import { Html, Button, Container, Text } from '@react-email/components';

interface WelcomeEmailProps {
  name: string;
  verificationUrl: string;
}

export default function WelcomeEmail({ name, verificationUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Container>
        <Text>Welcome, {name}!</Text>
        <Button href={verificationUrl}>Verify Email</Button>
      </Container>
    </Html>
  );
}
```

### Send Email (API Route)

```typescript
// app/api/send-welcome/route.ts
import { resend } from '@/lib/resend';
import WelcomeEmail from '@/emails/WelcomeEmail';

export async function POST(req: Request) {
  const { email, name, verificationUrl } = await req.json();

  try {
    const { data, error } = await resend.emails.send({
      from: 'MyApp <noreply@yourdomain.com>',
      to: email,
      subject: 'Welcome to MyApp!',
      react: WelcomeEmail({ name, verificationUrl }),
    });

    if (error) {
      return Response.json({ error }, { status: 500 });
    }

    return Response.json({ id: data.id });
  } catch (error) {
    return Response.json({ error: 'Failed to send email' }, { status: 500 });
  }
}
```

---

## .NET Usage

### Email Service Interface

```csharp
// Services/IEmailService.cs
public interface IEmailService
{
    Task<string> SendWelcomeEmailAsync(string toEmail, string name);
    Task<string> SendPasswordResetAsync(string toEmail, string resetUrl);
}
```

### Email Service Implementation

```csharp
// Services/EmailService.cs
using Resend;

public class EmailService : IEmailService
{
    private readonly IResend _resend;
    private readonly IConfiguration _config;

    public EmailService(IResend resend, IConfiguration config)
    {
        _resend = resend;
        _config = config;
    }

    public async Task<string> SendWelcomeEmailAsync(string toEmail, string name)
    {
        var message = new EmailMessage
        {
            From = _config["Resend:FromEmail"]!,
            To = toEmail,
            Subject = "Welcome!",
            HtmlBody = $@"
                <h1>Welcome, {name}!</h1>
                <p>Thank you for joining us.</p>
            "
        };

        var response = await _resend.EmailSendAsync(message);
        return response.Id;
    }

    public async Task<string> SendPasswordResetAsync(string toEmail, string resetUrl)
    {
        var message = new EmailMessage
        {
            From = _config["Resend:FromEmail"]!,
            To = toEmail,
            Subject = "Reset Your Password",
            HtmlBody = $@"
                <h1>Password Reset</h1>
                <p>Click the link below to reset your password:</p>
                <a href=""{resetUrl}"">Reset Password</a>
            "
        };

        var response = await _resend.EmailSendAsync(message);
        return response.Id;
    }
}

// Register in DI
builder.Services.AddScoped<IEmailService, EmailService>();
```

---

## Batch Emails

### Next.js Batch Send

```typescript
// Send to multiple recipients
const { data, error } = await resend.batch.send([
  {
    from: 'MyApp <noreply@yourdomain.com>',
    to: 'user1@example.com',
    subject: 'Notification',
    react: NotificationEmail({ message: 'Update 1' }),
  },
  {
    from: 'MyApp <noreply@yourdomain.com>',
    to: 'user2@example.com',
    subject: 'Notification',
    react: NotificationEmail({ message: 'Update 2' }),
  },
]);
```

---

## Webhooks for Delivery Status

### Setup Webhook Endpoint

Resend webhooks are signed with **Svix** — verify with `resend.webhooks.verify()`,
which checks the `svix-id` / `svix-timestamp` / `svix-signature` headers against
the raw request body. Do **not** roll a manual HMAC: Resend does not use a plain
`resend-signature` header, and a hand-written check will reject valid webhooks.

```typescript
// app/api/webhooks/resend/route.ts
import { resend } from '@/lib/resend';

export async function POST(req: Request) {
  // Raw body is required — verification fails on a re-serialized payload
  const payload = await req.text();

  let event;
  try {
    // Throws on an invalid signature; returns the parsed payload on success
    event = resend.webhooks.verify({
      payload,
      headers: {
        id: req.headers.get('svix-id')!,
        timestamp: req.headers.get('svix-timestamp')!,
        signature: req.headers.get('svix-signature')!,
      },
      webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
    });
  } catch {
    return new Response('Invalid signature', { status: 401 });
  }

  switch (event.type) {
    case 'email.delivered':
      await handleEmailDelivered(event.data);
      break;
    case 'email.bounced':
      await handleEmailBounced(event.data);
      break;
    case 'email.complained':
      await handleEmailComplained(event.data);
      break;
  }

  return new Response('OK');
}
```

---

## Best Practices

### Background Jobs (with Hangfire)

```csharp
// Don't block HTTP requests with email sending
BackgroundJob.Enqueue<IEmailService>(
    x => x.SendWelcomeEmailAsync(email, name));
```

### Rate Limiting

```typescript
// Implement rate limiting for email sends
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 h'), // 10 emails per hour
});

const { success } = await ratelimit.limit(userId);
if (!success) {
  return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
}

await resend.emails.send({ ... });
```

### Retry Logic (.NET)

```csharp
using Polly;

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt =>
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

await retryPolicy.ExecuteAsync(async () =>
{
    await _resend.EmailSendAsync(message);
});
```

---

## Domain Verification

1. Go to Resend Dashboard → Domains
2. Add your domain (e.g., `yourdomain.com`)
3. Add DNS records (SPF, DKIM, DMARC)
4. Wait for verification (up to 72 hours)

### DNS Records Example

```
TXT  @  v=spf1 include:resend.com ~all
TXT  resend._domainkey  <DKIM_VALUE>
TXT  _dmarc  v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com
```

---

## Common Email Templates

### Order Confirmation

```tsx
export default function OrderConfirmation({ orderNumber, total, items }) {
  return (
    <Html>
      <Container>
        <Text>Order #{orderNumber} Confirmed</Text>
        <Text>Total: ${total}</Text>
        <ul>
          {items.map(item => (
            <li key={item.id}>{item.name} - ${item.price}</li>
          ))}
        </ul>
      </Container>
    </Html>
  );
}
```

### Magic Link Auth

```tsx
export default function MagicLink({ url, expiresIn }) {
  return (
    <Html>
      <Container>
        <Text>Sign in to your account</Text>
        <Button href={url}>Sign In</Button>
        <Text>This link expires in {expiresIn} minutes.</Text>
      </Container>
    </Html>
  );
}
```

---

## References

- [Resend Documentation](https://resend.com/docs)
- [React Email](https://react.email/)
- [Resend .NET SDK](https://github.com/resend/resend-dotnet)

---

*MORPH-SPEC by Polymorphism Tech*
