Skip to main content

Resend Email Service

upLegal uses Resend for all transactional email delivery, including booking confirmations, welcome emails, and lawyer notifications.

Setup

Environment Variables

Configure your Resend API key:
RESEND_API_KEY=re_your_api_key_here
# Or for Vite projects:
VITE_RESEND_API_KEY=re_your_api_key_here
Get your API key from resend.com/api-keys after creating an account.

Domain Configuration

Verify your sending domain in Resend:
  1. Add your domain (e.g., legalup.cl)
  2. Add DNS records for verification
  3. Use verified domains in the from field:

Installation

npm install resend

Basic Usage

Initialize Resend Client

import { Resend } from 'resend';

const resend = new Resend(import.meta.env.RESEND_API_KEY);

Send Simple Email

const { data, error } = await resend.emails.send({
  from: 'LegalUp <[email protected]>',
  to: '[email protected]',
  subject: 'Bienvenido a LegalUp',
  html: '<h1>¡Hola!</h1><p>Gracias por registrarte.</p>',
});

if (error) {
  console.error('Error sending email:', error);
} else {
  console.log('Email sent:', data.id);
}

Email Templates

Welcome Email

Send welcome emails to new users:
// src/lib/emails/welcomeEmail.ts
import { Resend } from 'resend';

const resend = new Resend(import.meta.env.RESEND_API_KEY);

export async function sendWelcomeEmail(email: string, name: string) {
  try {
    const { data, error } = await resend.emails.send({
      from: 'Bienvenido a LegalUp <[email protected]>',
      to: email,
      subject: '¡Bienvenido a LegalUp! Confirma tu correo electrónico',
      html: `
        <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
          <h1 style="color: #2563eb;">¡Bienvenido a LegalUp, ${name}!</h1>
          <p>Gracias por registrarte en nuestra plataforma. Estamos encantados de tenerte con nosotros.</p>
          <p>Para comenzar a utilizar todos los beneficios de LegalUp, por favor confirma tu dirección de correo electrónico haciendo clic en el siguiente botón:</p>
          <a href="${window.location.origin}/auth/confirm-email" 
             style="display: inline-block; padding: 12px 24px; background-color: #2563eb; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0;">
            Confirmar correo electrónico
          </a>
          <p>Si no creaste esta cuenta, puedes ignorar este correo.</p>
          <p>¡Esperamos que disfrutes de nuestra plataforma!</p>
          <p>El equipo de LegalUp</p>
        </div>
      `,
    });

    return { data, error };
  } catch (error) {
    return { error };
  }
}

Lawyer Services Email

Prompt lawyers to add their services:
// src/lib/emails/sendLawyerServicesEmail.ts
import { Resend } from 'resend';
import { getLawyerServicesEmail } from './lawyerServicesEmail';

const getResendApiKey = () => {
  const key = import.meta.env?.VITE_RESEND_API_KEY || process.env?.RESEND_API_KEY;
  if (!key) {
    throw new Error('VITE_RESEND_API_KEY is not set');
  }
  return key;
};

export async function sendLawyerServicesEmail(to: string, lawyerName: string) {
  try {
    const resend = new Resend(getResendApiKey());
    
    const { data, error } = await resend.emails.send({
      from: 'LegalUp <[email protected]>',
      to: [to],
      subject: '¡Completa tu perfil en LegalUp!',
      html: getLawyerServicesEmail(lawyerName),
    });

    if (error) {
      console.error('❌ Error sending email:', error);
      return { success: false, error };
    }

    console.log('✅ Email sent successfully');
    return { success: true, data };
  } catch (error) {
    console.error('❌ Unexpected error:', error);
    return { success: false, error };
  }
}

Lawyer Services Email Template

// src/lib/emails/lawyerServicesEmail.ts
export function getLawyerServicesEmail(lawyerName: string) {
  return `
    <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
      <div style="text-align: center; margin-bottom: 20px;">
        <h1 style="color: #2563eb;">¡Hola ${lawyerName}!</h1>
      </div>
      
      <p>¡Bienvenido a LegalUp! Notamos que aún no has agregado tus servicios a la plataforma.</p>
      
      <p>Agregar tus servicios es una excelente manera de:</p>
      <ul style="margin: 15px 0; padding-left: 20px;">
        <li>Mostrar tus especialidades legales</li>
        <li>Atraer a más clientes</li>
        <li>Destacar entre otros abogados</li>
      </ul>

      <div style="background: #f8fafc; padding: 15px; border-radius: 8px; margin: 20px 0;">
        <h3 style="margin-top: 0;">Ejemplo de servicio destacado:</h3>
        <p><strong>Nombre del servicio:</strong> Asesoría Laboral Inicial</p>
        <p><strong>Descripción:</strong> Asesoría personalizada sobre temas laborales, incluyendo despidos, finiquitos y derechos del trabajador.</p>
        <p><strong>Duración:</strong> 60 minutos</p>
        <p><strong>Precio:</strong> $50.000 CLP</p>
      </div>

      <div style="text-align: center; margin: 30px 0;">
        <a href="https://legalup.cl/dashboard/services" 
           style="display: inline-block; padding: 12px 24px; background-color: #2563eb; color: white; text-decoration: none; border-radius: 6px; font-weight: 500;">
          Agregar mis servicios ahora
        </a>
      </div>

      <p>Si necesitas ayuda para configurar tus servicios, no dudes en responder a este correo.</p>
      
      <p>¡Esperamos verte pronto en la plataforma!</p>
      
      <p>Saludos,<br>El equipo de LegalUp</p>
      
      <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #64748b;">
        <p>© ${new Date().getFullYear()} LegalUp. Todos los derechos reservados.</p>
        <p>Si no deseas recibir más correos como este, <a href="https://legalup.cl/preferencias" style="color: #2563eb;">actualiza tus preferencias</a>.</p>
      </div>
    </div>
  `;
}

Server-Side Email Sending

Booking Confirmation Email

Send confirmation emails after successful payment:
// server.mjs
import { Resend } from 'resend';

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

// In webhook handler
app.post('/api/mercadopago/webhook', async (req, res) => {
  // ... payment processing ...
  
  if (payment.status === 'approved') {
    // Generate magic link
    const { data: linkData } = await supabase.auth.admin.generateLink({
      type: 'magiclink',
      email: userEmail,
      options: { redirectTo: `${appUrl}/dashboard/appointments` },
    });
    
    const magicLink = linkData.properties.action_link;
    
    // Send to client
    await resend.emails.send({
      from: 'LegalUp <[email protected]>',
      to: userEmail,
      subject: '¡Tu asesoría está confirmada!',
      html: `
        <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
          <h1 style="color: #2563eb;">¡Reserva Confirmada!</h1>
          <p>Hola <strong>${userName}</strong>,</p>
          <p>Tu asesoría ha sido confirmada exitosamente.</p>
          
          <div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0;">
            <p style="margin: 5px 0;"><strong>Fecha:</strong> ${booking.scheduled_date}</p>
            <p style="margin: 5px 0;"><strong>Hora:</strong> ${booking.scheduled_time}</p>
            <p style="margin: 5px 0;"><strong>Duración:</strong> ${booking.duration} min</p>
          </div>

          <div style="text-align: center; margin: 30px 0;">
            <a href="${magicLink}" style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold;">
              Ingresar a mi cuenta y ver detalles
            </a>
          </div>
        </div>
      `,
    });
    
    // Send to lawyer
    await resend.emails.send({
      from: 'LegalUp <[email protected]>',
      to: lawyerEmail,
      subject: 'Nueva reserva confirmada',
      html: `
        <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
          <h1 style="color: #2563eb;">¡Nueva Reserva!</h1>
          <p>Has recibido una nueva reserva confirmada.</p>
          
          <div style="background-color: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0;">
            <p style="margin: 5px 0;"><strong>Cliente:</strong> ${userName}</p>
            <p style="margin: 5px 0;"><strong>Email:</strong> ${userEmail}</p>
            <p style="margin: 5px 0;"><strong>Fecha:</strong> ${booking.scheduled_date}</p>
            <p style="margin: 5px 0;"><strong>Hora:</strong> ${booking.scheduled_time}</p>
          </div>

          <div style="text-align: center; margin: 30px 0;">
            <a href="${appUrl}/dashboard/appointments" style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold;">
              Ir a mis citas
            </a>
          </div>
        </div>
      `,
    });
  }
});

Error Handling

Always handle Resend errors gracefully:
try {
  const { data, error } = await resend.emails.send({
    from: 'LegalUp <[email protected]>',
    to: email,
    subject: 'Test Email',
    html: '<p>Test</p>',
  });

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

  console.log('Email sent successfully:', data.id);
  return { success: true, messageId: data.id };
} catch (error) {
  console.error('Unexpected error sending email:', error);
  return { success: false, error: 'Failed to send email' };
}

Bulk Email Sending

Notify multiple lawyers:
app.post('/api/admin/notify-lawyers', async (req, res) => {
  const { data: profiles } = await supabase
    .from('profiles')
    .select('email, first_name, last_name')
    .eq('role', 'lawyer')
    .is('has_services', false);

  const results = [];
  
  for (const profile of profiles) {
    try {
      await resend.emails.send({
        from: 'LegalUp <[email protected]>',
        to: profile.email,
        subject: '¡Completa tu perfil!',
        html: getLawyerServicesEmail(profile.first_name),
      });
      
      results.push({ email: profile.email, success: true });
    } catch (error) {
      results.push({ email: profile.email, success: false, error });
    }
  }
  
  res.json({ results });
});

Best Practices

  • Always use verified domains in the from field
  • Include unsubscribe links in marketing emails
  • Use descriptive subject lines
  • Test emails before sending to users
  • Log email IDs for tracking
  • Handle errors gracefully
  • Use environment variables for API keys
  • Separate email templates from logic

Testing

Test email delivery in development:
const testEmail = async () => {
  const result = await sendWelcomeEmail(
    '[email protected]',
    'Test User'
  );
  
  if (result.error) {
    console.error('Test failed:', result.error);
  } else {
    console.log('Test email sent:', result.data.id);
  }
};

Rate Limits

Resend rate limits:
  • Free tier: 100 emails/day
  • Paid plans: Higher limits based on plan
Implement rate limiting:
const emailQueue = [];
let lastSent = Date.now();

const sendWithRateLimit = async (emailData) => {
  const now = Date.now();
  const timeSinceLastEmail = now - lastSent;
  
  if (timeSinceLastEmail < 1000) {
    await new Promise(resolve => setTimeout(resolve, 1000 - timeSinceLastEmail));
  }
  
  const result = await resend.emails.send(emailData);
  lastSent = Date.now();
  
  return result;
};

Build docs developers (and LLMs) love