Skip to content

Resend Email Service Migration Guide

Date: November 7, 2025 Purpose: Migrate from Spacemail (non-functional SMTP) to Resend (working API) Estimated Time: 15-20 minutes Risk: Zero (test before canceling Spacemail)


Why Resend?

Spacemail Issues Discovered: - Connection timeout on smtp.spacemail.com (ports 465, 587) - Certificate mismatch on smtp.spacemail.io (uses AWS SES but unclear credentials) - No SDK or REST API available - Poor documentation

Resend Benefits: - โœ… Simple 3-line API (no SMTP configuration) - โœ… Excellent documentation - โœ… Official Node.js SDK - โœ… Free tier: 100 emails/day, 3,000/month - โœ… $20/month for 50,000 emails (beyond free tier) - โœ… 5-minute setup


Step 1: Sign Up for Resend

  1. Go to https://resend.com
  2. Click "Sign Up" (top right)
  3. Sign up with GitHub (easiest) or email
  4. Verify your email address
  5. Complete onboarding

Time: 2 minutes


Step 2: Get API Key

  1. Once logged in, go to API Keys section (left sidebar)
  2. Click "Create API Key"
  3. Name: CodeSlick Production
  4. Permission: Full Access (or "Sending Access" if available)
  5. Click "Create"
  6. Copy the API key (starts with re_...)
  7. Save it somewhere safe (you'll only see it once)

Example: re_123abc456def789ghi012jkl345mno678pqr

Time: 1 minute


Step 3: Verify Your Domain

3.1: Add Domain to Resend

  1. In Resend dashboard, go to Domains section
  2. Click "Add Domain"
  3. Enter: codeslick.dev
  4. Click "Add"

3.2: Get DNS Records

Resend will provide 3 DNS records (yours will be different):

Record 1: SPF (TXT)

Type: TXT
Name: @
Value: v=spf1 include:resend.com ~all

Record 2: DKIM (TXT)

Type: TXT
Name: resend._domainkey
Value: k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...

Record 3: Domain Verification (TXT)

Type: TXT
Name: _resend
Value: resend-verify-abc123xyz456

3.3: Add DNS Records to Your Domain Registrar

Where is codeslick.dev registered? (Namecheap, GoDaddy, Cloudflare, etc.)

Example for Namecheap: 1. Log in to Namecheap 2. Go to Domain List โ†’ codeslick.dev โ†’ Manage 3. Click "Advanced DNS" tab 4. Click "Add New Record" 5. Add all 3 TXT records shown in Resend dashboard 6. Save changes

DNS Propagation: Wait 5-15 minutes (usually instant, max 1 hour)

3.4: Verify Domain in Resend

  1. Back in Resend dashboard โ†’ Domains
  2. Click "Verify" next to codeslick.dev
  3. If verified: โœ… You'll see "Verified" status
  4. If not verified yet: Wait 5 more minutes, try again

Time: 5-10 minutes (including DNS propagation)


Step 4: Install Resend SDK

npm install resend

Time: 30 seconds


Step 5: Update Environment Variables

Local Development (.env.local)

Add these new variables:

# Resend Email Service (Production)
RESEND_API_KEY=re_your_api_key_here
RESEND_FROM_EMAIL=support@codeslick.dev
RESEND_FROM_NAME=CodeSlick

# Keep Mailtrap for local testing (optional)
MAILTRAP_HOST=sandbox.smtp.mailtrap.io
MAILTRAP_PORT=2525
MAILTRAP_USER=your_mailtrap_user

Remove or comment out Spacemail variables (keep as backup):

# OLD - Spacemail (not working, keeping as backup)
# SPACEMAIL_SECRET=xPYzhJ1ZVhmXVwQNKhWoAxVOiiuT0bCXlD8WrYIfNtmlbujUUYmNqScSFA270Bwp
# SPACEMAIL_FROM_EMAIL=support@codeslick.dev
# SPACEMAIL_FROM_NAME=CodeSlick

Production (Vercel)

  1. Go to Vercel โ†’ Your Project โ†’ Settings โ†’ Environment Variables
  2. Add new variables:
  3. RESEND_API_KEY = re_your_api_key_here
  4. RESEND_FROM_EMAIL = support@codeslick.dev
  5. RESEND_FROM_NAME = CodeSlick
  6. Keep existing variables for now (as backup):
  7. SPACEMAIL_SECRET (don't delete yet)
  8. SPACEMAIL_FROM_EMAIL (don't delete yet)
  9. SPACEMAIL_FROM_NAME (don't delete yet)
  10. Click "Save"
  11. Redeploy (Vercel will prompt you)

Time: 2 minutes


Step 6: Create Resend Email Client

Create new file: src/lib/email/resend-client.ts

/**
 * Resend Email Client
 *
 * Sends transactional emails using Resend API
 * - Team invitations
 * - Welcome emails
 * - Payment confirmations
 *
 * Part of Phase 5 Week 2 - Email Service Migration
 */

import { Resend } from 'resend';

// Initialize Resend client
const resend = new Resend(process.env.RESEND_API_KEY);

/**
 * Send team invitation email
 */
export async function sendTeamInvitationEmail({
  to,
  teamName,
  inviterName,
  invitationUrl,
}: {
  to: string;
  teamName: string;
  inviterName: string;
  invitationUrl: string;
}) {
  try {
    const { data, error } = await resend.emails.send({
      from: `${process.env.RESEND_FROM_NAME} <${process.env.RESEND_FROM_EMAIL}>`,
      to: [to],
      subject: `You've been invited to join ${teamName} on CodeSlick`,
      html: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f5f5f5;">
          <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 20px 0;">
            <tr>
              <td align="center">
                <table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
                  <!-- Header -->
                  <tr>
                    <td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 20px; text-align: center;">
                      <h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: bold;">
                        CodeSlick
                      </h1>
                    </td>
                  </tr>

                  <!-- Content -->
                  <tr>
                    <td style="padding: 40px 30px;">
                      <h2 style="color: #333333; margin: 0 0 20px 0; font-size: 24px;">
                        You've been invited to ${teamName}
                      </h2>
                      <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 15px 0;">
                        ${inviterName} has invited you to join their team on CodeSlick.
                      </p>
                      <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 30px 0;">
                        CodeSlick provides automated security reviews for your GitHub pull requests, helping you catch vulnerabilities before they reach production.
                      </p>

                      <!-- CTA Button -->
                      <table width="100%" cellpadding="0" cellspacing="0">
                        <tr>
                          <td align="center" style="padding: 20px 0;">
                            <a href="${invitationUrl}"
                               style="background-color: #667eea;
                                      color: #ffffff;
                                      text-decoration: none;
                                      padding: 14px 40px;
                                      border-radius: 6px;
                                      font-size: 16px;
                                      font-weight: bold;
                                      display: inline-block;">
                              Accept Invitation
                            </a>
                          </td>
                        </tr>
                      </table>

                      <p style="color: #999999; font-size: 14px; line-height: 1.6; margin: 30px 0 0 0; text-align: center;">
                        This invitation will expire in 7 days
                      </p>
                    </td>
                  </tr>

                  <!-- Footer -->
                  <tr>
                    <td style="background-color: #f8f8f8; padding: 30px; text-align: center; border-top: 1px solid #eeeeee;">
                      <p style="color: #999999; font-size: 12px; line-height: 1.6; margin: 0;">
                        CodeSlick - Automated Security Reviews for GitHub PRs<br>
                        <a href="https://codeslick.dev" style="color: #667eea; text-decoration: none;">codeslick.dev</a>
                      </p>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </body>
        </html>
      `,
      text: `
You've been invited to ${teamName} on CodeSlick

${inviterName} has invited you to join their team on CodeSlick.

CodeSlick provides automated security reviews for your GitHub pull requests.

Accept your invitation: ${invitationUrl}

This invitation will expire in 7 days.

---
CodeSlick - Automated Security Reviews for GitHub PRs
https://codeslick.dev
      `.trim(),
    });

    if (error) {
      console.error('โŒ Resend API error:', error);
      return { success: false, error };
    }

    console.log(`โœ… Invitation email sent to ${to} (Message ID: ${data?.id})`);
    return { success: true, messageId: data?.id };
  } catch (error) {
    console.error('โŒ Failed to send invitation email:', error);
    return { success: false, error };
  }
}

/**
 * Send welcome email to new users
 */
export async function sendWelcomeEmail({
  to,
  userName,
}: {
  to: string;
  userName: string;
}) {
  try {
    const { data, error } = await resend.emails.send({
      from: `${process.env.RESEND_FROM_NAME} <${process.env.RESEND_FROM_EMAIL}>`,
      to: [to],
      subject: 'Welcome to CodeSlick!',
      html: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f5f5f5;">
          <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 20px 0;">
            <tr>
              <td align="center">
                <table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
                  <!-- Header -->
                  <tr>
                    <td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 20px; text-align: center;">
                      <h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: bold;">
                        Welcome to CodeSlick!
                      </h1>
                    </td>
                  </tr>

                  <!-- Content -->
                  <tr>
                    <td style="padding: 40px 30px;">
                      <h2 style="color: #333333; margin: 0 0 20px 0; font-size: 24px;">
                        Hi ${userName}!
                      </h2>
                      <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 15px 0;">
                        Thanks for signing up. You're all set to start securing your code.
                      </p>

                      <h3 style="color: #333333; margin: 30px 0 15px 0; font-size: 20px;">
                        Next Steps:
                      </h3>
                      <ol style="color: #666666; font-size: 16px; line-height: 1.8; margin: 0 0 30px 0; padding-left: 20px;">
                        <li>Install the CodeSlick GitHub App</li>
                        <li>Create or join a team</li>
                        <li>Open a pull request to see CodeSlick in action</li>
                      </ol>

                      <!-- CTA Button -->
                      <table width="100%" cellpadding="0" cellspacing="0">
                        <tr>
                          <td align="center" style="padding: 20px 0;">
                            <a href="https://codeslick.dev/teams"
                               style="background-color: #667eea;
                                      color: #ffffff;
                                      text-decoration: none;
                                      padding: 14px 40px;
                                      border-radius: 6px;
                                      font-size: 16px;
                                      font-weight: bold;
                                      display: inline-block;">
                              Get Started
                            </a>
                          </td>
                        </tr>
                      </table>

                      <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 30px 0 0 0;">
                        Need help? Reply to this email or check our
                        <a href="https://codeslick.dev/help" style="color: #667eea; text-decoration: none;">documentation</a>.
                      </p>
                    </td>
                  </tr>

                  <!-- Footer -->
                  <tr>
                    <td style="background-color: #f8f8f8; padding: 30px; text-align: center; border-top: 1px solid #eeeeee;">
                      <p style="color: #999999; font-size: 12px; line-height: 1.6; margin: 0;">
                        CodeSlick - Automated Security Reviews for GitHub PRs<br>
                        <a href="https://codeslick.dev" style="color: #667eea; text-decoration: none;">codeslick.dev</a>
                      </p>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </body>
        </html>
      `,
      text: `
Welcome to CodeSlick, ${userName}!

Thanks for signing up. You're all set to start securing your code.

Next Steps:
1. Install the CodeSlick GitHub App
2. Create or join a team
3. Open a pull request to see CodeSlick in action

Get Started: https://codeslick.dev/teams

Need help? Reply to this email or check our documentation: https://codeslick.dev/help

---
CodeSlick - Automated Security Reviews for GitHub PRs
https://codeslick.dev
      `.trim(),
    });

    if (error) {
      console.error('โŒ Resend API error:', error);
      return { success: false, error };
    }

    console.log(`โœ… Welcome email sent to ${to} (Message ID: ${data?.id})`);
    return { success: true, messageId: data?.id };
  } catch (error) {
    console.error('โŒ Failed to send welcome email:', error);
    return { success: false, error };
  }
}

/**
 * Send payment confirmation email
 */
export async function sendPaymentConfirmationEmail({
  to,
  teamName,
  amount,
  planName,
}: {
  to: string;
  teamName: string;
  amount: number;
  planName: string;
}) {
  const formattedAmount = `โ‚ฌ${(amount / 100).toFixed(2)}`;

  try {
    const { data, error } = await resend.emails.send({
      from: `${process.env.RESEND_FROM_NAME} <${process.env.RESEND_FROM_EMAIL}>`,
      to: [to],
      subject: 'Payment Confirmed - CodeSlick',
      html: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f5f5f5;">
          <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 20px 0;">
            <tr>
              <td align="center">
                <table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
                  <!-- Header -->
                  <tr>
                    <td style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); padding: 40px 20px; text-align: center;">
                      <h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: bold;">
                        Payment Confirmed
                      </h1>
                    </td>
                  </tr>

                  <!-- Content -->
                  <tr>
                    <td style="padding: 40px 30px;">
                      <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 30px 0;">
                        Thank you for your payment! Your ${planName} subscription for <strong>${teamName}</strong> is now active.
                      </p>

                      <table width="100%" cellpadding="10" cellspacing="0" style="background-color: #f8f8f8; border-radius: 6px; margin: 0 0 30px 0;">
                        <tr>
                          <td style="color: #666666; font-size: 14px; padding: 15px;">
                            <strong>Plan:</strong> ${planName}
                          </td>
                        </tr>
                        <tr>
                          <td style="color: #666666; font-size: 14px; padding: 15px; border-top: 1px solid #e5e5e5;">
                            <strong>Amount:</strong> ${formattedAmount}/month
                          </td>
                        </tr>
                        <tr>
                          <td style="color: #666666; font-size: 14px; padding: 15px; border-top: 1px solid #e5e5e5;">
                            <strong>Team:</strong> ${teamName}
                          </td>
                        </tr>
                      </table>

                      <!-- CTA Button -->
                      <table width="100%" cellpadding="0" cellspacing="0">
                        <tr>
                          <td align="center" style="padding: 20px 0;">
                            <a href="https://codeslick.dev/teams"
                               style="background-color: #667eea;
                                      color: #ffffff;
                                      text-decoration: none;
                                      padding: 14px 40px;
                                      border-radius: 6px;
                                      font-size: 16px;
                                      font-weight: bold;
                                      display: inline-block;">
                              Go to Dashboard
                            </a>
                          </td>
                        </tr>
                      </table>

                      <p style="color: #999999; font-size: 14px; line-height: 1.6; margin: 30px 0 0 0; text-align: center;">
                        You can manage your subscription anytime in your team settings
                      </p>
                    </td>
                  </tr>

                  <!-- Footer -->
                  <tr>
                    <td style="background-color: #f8f8f8; padding: 30px; text-align: center; border-top: 1px solid #eeeeee;">
                      <p style="color: #999999; font-size: 12px; line-height: 1.6; margin: 0;">
                        CodeSlick - Automated Security Reviews for GitHub PRs<br>
                        <a href="https://codeslick.dev" style="color: #667eea; text-decoration: none;">codeslick.dev</a>
                      </p>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </body>
        </html>
      `,
      text: `
Payment Confirmed

Thank you for your payment! Your ${planName} subscription for ${teamName} is now active.

Plan: ${planName}
Amount: ${formattedAmount}/month
Team: ${teamName}

Go to Dashboard: https://codeslick.dev/teams

You can manage your subscription anytime in your team settings.

---
CodeSlick - Automated Security Reviews for GitHub PRs
https://codeslick.dev
      `.trim(),
    });

    if (error) {
      console.error('โŒ Resend API error:', error);
      return { success: false, error };
    }

    console.log(`โœ… Payment confirmation sent to ${to} (Message ID: ${data?.id})`);
    return { success: true, messageId: data?.id };
  } catch (error) {
    console.error('โŒ Failed to send payment confirmation:', error);
    return { success: false, error };
  }
}

Time: Copy-paste (1 minute)


Step 7: Update Team Invitation API Endpoint

Update: src/app/api/teams/[id]/invitations/route.ts

Find this section (around line 272):

// Send invitation email via Spacemail (non-blocking)
if (process.env.SPACEMAIL_SECRET && process.env.SPACEMAIL_FROM_EMAIL) {
  sendTeamInvitationEmail({
    to: email,
    teamName: team.name,
    inviterName: auth.user.name || auth.user.email || 'A team member',
    invitationUrl: inviteUrl,
  }).catch((error) => {
    // Log error but don't block the API response
    console.error('[Team Invitations API] Failed to send invitation email:', error);
  });
}

Replace the import at the top:

// OLD:
import { sendTeamInvitationEmail } from '@/lib/email/spacemail-smtp-client';

// NEW:
import { sendTeamInvitationEmail } from '@/lib/email/resend-client';

Update the email sending logic:

// Send invitation email via Resend (non-blocking)
if (process.env.RESEND_API_KEY && process.env.RESEND_FROM_EMAIL) {
  sendTeamInvitationEmail({
    to: email,
    teamName: team.name,
    inviterName: auth.user.name || auth.user.email || 'A team member',
    invitationUrl: inviteUrl,
  }).catch((error) => {
    // Log error but don't block the API response
    console.error('[Team Invitations API] Failed to send invitation email:', error);
  });
} else {
  // Development fallback: log invitation URL
  console.log('๐Ÿ“ง INVITATION EMAIL (DEV MODE - no email service configured)');
  console.log(`   To: ${email}`);
  console.log(`   Team: ${team.name}`);
  console.log(`   Inviter: ${auth.user.name || auth.user.email}`);
  console.log(`   URL: ${inviteUrl}`);
}

Time: 2 minutes


Create test script: scripts/test-resend-email.ts

/**
 * Test Resend Email Sending
 *
 * Usage:
 * npx tsx scripts/test-resend-email.ts
 */

import { loadEnvConfig } from '@next/env';
import { sendTeamInvitationEmail } from '../src/lib/email/resend-client';

loadEnvConfig(process.cwd());

async function testResendEmail() {
  console.log('๐Ÿงช Testing Resend email configuration...\n');

  // Check environment variables
  console.log('Environment Variables:');
  console.log(`  RESEND_API_KEY: ${process.env.RESEND_API_KEY ? '***SET***' : 'NOT SET'}`);
  console.log(`  RESEND_FROM_EMAIL: ${process.env.RESEND_FROM_EMAIL || 'NOT SET'}`);
  console.log(`  RESEND_FROM_NAME: ${process.env.RESEND_FROM_NAME || 'NOT SET'}\n`);

  if (!process.env.RESEND_API_KEY || !process.env.RESEND_FROM_EMAIL) {
    console.error('โŒ Missing required environment variables');
    console.error('   Required: RESEND_API_KEY, RESEND_FROM_EMAIL');
    process.exit(1);
  }

  // Send test email
  console.log('๐Ÿ“ง Sending test invitation email...\n');

  const result = await sendTeamInvitationEmail({
    to: 'vclourenco@protonmail.ch', // Your email
    teamName: 'Test Team',
    inviterName: 'Resend Test Script',
    invitationUrl: 'https://codeslick.dev/invite/test-token-resend-123',
  });

  if (result.success) {
    console.log('\nโœ… Email sent successfully via Resend!');
    console.log(`   Message ID: ${result.messageId}`);
    console.log('\n๐ŸŽ‰ Resend is working! Check your inbox (vclourenco@protonmail.ch)');
  } else {
    console.log('\nโŒ Email failed to send');
    console.error('   Error:', result.error);
  }
}

testResendEmail().catch(console.error);

Run test:

npx tsx scripts/test-resend-email.ts

Expected output:

โœ… Email sent successfully via Resend!
   Message ID: re_abc123xyz456

Check your email (vclourenco@protonmail.ch) - you should receive the invitation within 5-10 seconds.

Time: 3 minutes


Step 9: Deploy to Production

9.1: Commit Changes

git add .
git commit -m "feat: Migrate from Spacemail to Resend email service

- Install resend SDK
- Create resend-client.ts with 3 email templates
- Update team invitation API to use Resend
- Add RESEND_API_KEY environment variable support
- Test script confirms email delivery working
- Fixes: Email invitations not being delivered in production"

9.2: Push to Vercel

git push origin main

Vercel will automatically deploy (3-5 minutes).

9.3: Verify Environment Variables in Vercel

  1. Go to Vercel โ†’ Project โ†’ Settings โ†’ Environment Variables
  2. Confirm these exist:
  3. โœ… RESEND_API_KEY
  4. โœ… RESEND_FROM_EMAIL
  5. โœ… RESEND_FROM_NAME

Time: 5 minutes


Step 10: Test in Production

10.1: Send Test Invitation

  1. Go to https://codeslick.dev
  2. Sign in
  3. Go to your team โ†’ Settings โ†’ Members
  4. Click "Invite Member"
  5. Enter: vclourenco@protonmail.ch
  6. Click "Send Invitation"

10.2: Verify Email Received

  • Check your inbox (vclourenco@protonmail.ch)
  • Email should arrive within 5-10 seconds
  • Check spam folder if not in inbox

10.3: Check Resend Dashboard

  1. Go to Resend dashboard โ†’ Emails (or Logs)
  2. You should see the sent email with status "Delivered"
  3. Click on email to see full details

Time: 2 minutes


Step 11: Clean Up (After Confirming Resend Works)

11.1: Remove Spacemail Environment Variables

In Vercel: 1. Go to Settings โ†’ Environment Variables 2. Delete: - SPACEMAIL_SECRET - SPACEMAIL_FROM_EMAIL (if separate from RESEND) - SPACEMAIL_FROM_NAME (if separate from RESEND)

In .env.local:

# Comment out or delete Spacemail variables
# SPACEMAIL_SECRET=...

11.2: Remove Old Files (Optional)

# Rename old file for reference
mv src/lib/email/spacemail-smtp-client.ts src/lib/email/spacemail-smtp-client.ts.backup

# Or delete if confident
rm src/lib/email/spacemail-smtp-client.ts
rm scripts/test-email.ts
rm scripts/test-spacemail-smtp.ts

11.3: Cancel Spacemail Subscription

  1. Log in to Spacemail
  2. Go to Billing or Subscription settings
  3. Cancel subscription
  4. Confirm cancellation

Time: 3 minutes


Troubleshooting

Issue 1: "Invalid API key" Error

Problem: RESEND_API_KEY is incorrect or not set

Fix: 1. Go to Resend dashboard โ†’ API Keys 2. Verify your API key 3. Copy it again (create new one if needed) 4. Update .env.local or Vercel environment variables 5. Restart dev server or redeploy

Issue 2: "Domain not verified" Error

Problem: codeslick.dev not verified in Resend

Fix: 1. Check DNS records are correctly added 2. Use DNS checker: https://dnschecker.org - Search for: codeslick.dev (TXT records) - Should show Resend verification records 3. Wait 5-15 more minutes for propagation 4. Click "Verify" in Resend dashboard

Issue 3: Emails Going to Spam

Problem: SPF/DKIM records not configured correctly

Fix: 1. Verify all 3 DNS records are added (SPF, DKIM, verification) 2. Add DMARC policy (optional but helps):

Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none; rua=mailto:support@codeslick.dev
3. Test email delivery with https://www.mail-tester.com

Issue 4: "Rate limit exceeded" Error

Problem: Exceeded free tier (100 emails/day)

Fix: 1. Check Resend dashboard โ†’ Usage 2. Upgrade to paid plan if needed ($20/month for 50k emails) 3. Implement rate limiting in application


Cost Comparison

Service Free Tier Paid Tier Current Status
Spacemail Unknown Unknown Not working (SMTP timeout)
Resend 100 emails/day
3,000/month
$20/month
50,000 emails
โœ… Recommended

Beta Launch Estimate: - 10 beta users ร— 1 invitation = 10 emails - 10 welcome emails = 10 emails - 5 payment confirmations = 5 emails - Total: ~25 emails (well within free tier)

Month 1 Estimate (after beta): - 50 new users ร— 2 emails (welcome + invitation) = 100 emails - Total: ~100 emails/month (free tier sufficient)


Migration Checklist

  • Step 1: Sign up for Resend account
  • Step 2: Get API key from dashboard
  • Step 3: Verify domain (add 3 DNS records)
  • Step 4: Install resend SDK (npm install resend)
  • Step 5: Add environment variables (local + Vercel)
  • Step 6: Create resend-client.ts file
  • Step 7: Update invitation API endpoint
  • Step 8: Test locally with test script
  • Step 9: Deploy to production (commit + push)
  • Step 10: Test in production (send real invitation)
  • Step 11: Clean up (remove Spacemail variables)
  • Step 12: Cancel Spacemail subscription

Success Criteria

โœ… Email sent successfully in local test โœ… Email sent successfully in production โœ… Email arrives within 10 seconds โœ… Email not in spam folder โœ… Resend dashboard shows "Delivered" status โœ… Production invitation flow works end-to-end


Next Steps After Migration

  1. Test all 3 email types:
  2. Team invitation โœ…
  3. Welcome email (on sign-up)
  4. Payment confirmation (after Stripe payment)

  5. Monitor email delivery:

  6. Check Resend dashboard daily
  7. Monitor bounce rates
  8. Watch for spam complaints

  9. Set up email templates in Resend (optional):

  10. Go to Resend โ†’ Templates
  11. Create reusable templates
  12. Reference templates by ID instead of HTML strings

Support Resources

Resend Documentation: https://resend.com/docs/introduction Resend Node.js SDK: https://resend.com/docs/send-with-nodejs Resend Status Page: https://status.resend.com DNS Checker: https://dnschecker.org Email Tester: https://www.mail-tester.com


Document Created: November 7, 2025 Migration Status: Ready to Execute Estimated Total Time: 15-20 minutes Risk Level: Zero (test before canceling Spacemail)

Next Action: Execute Step 1 (Sign up for Resend)