Yaltopia-Ticket-Email/docs/INTEGRATION_GUIDE.md

12 KiB

Integration Guide for Yaltopia Backend

This email service is designed to be called by your Yaltopia backend with the data you already have. No complex API integrations needed!

🚀 Quick Integration

1. Deploy the Email Service

# Using Docker (recommended)
docker-compose up -d

# Or Node.js
npm run build && npm start

2. Configure Environment

RESEND_API_KEY=re_your_resend_api_key
FROM_DOMAIN=yaltopia.com
FROM_EMAIL=noreply@yaltopia.com

3. Call from Your Backend

📧 Available Endpoints

Send Event Invitation

Endpoint: POST /api/emails/invitation

When to use: When user registers for an event or you want to send event invitations

// Example from your Yaltopia backend
async function sendEventInvitation(user, event) {
  const emailData = {
    to: user.email,
    eventName: event.name,
    dateTime: formatEventDateTime(event.startDate),
    location: event.location,
    ctaUrl: `https://yaltopia.com/events/${event.id}/rsvp`,
    ctaLabel: "RSVP Now",
    company: {
      name: "Yaltopia Ticket",
      logoUrl: "https://yaltopia.com/logo.png",
      primaryColor: "#f97316"
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/invitation`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  return response.json()
}

Send Team Member Invitation

Endpoint: POST /api/emails/team-invitation

When to use: When inviting users to join a team

async function sendTeamInvitation(inviter, recipient, team, invitationToken) {
  const emailData = {
    to: recipient.email,
    recipientName: recipient.name,
    inviterName: inviter.name,
    teamName: team.name,
    invitationLink: `https://yaltopia.com/teams/join?token=${invitationToken}`,
    customMessage: "Join our team and let's build something amazing together!",
    company: {
      name: "Yaltopia Ticket",
      logoUrl: "https://yaltopia.com/logo.png",
      primaryColor: "#10b981"
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/team-invitation`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  return response.json()
}

Send Invoice Share

Endpoint: POST /api/emails/invoice-share

When to use: When sharing invoices with clients or stakeholders

async function sendInvoiceShare(sender, recipient, invoice, shareToken) {
  const emailData = {
    to: recipient.email,
    recipientName: recipient.name,
    senderName: sender.name,
    invoiceNumber: invoice.number,
    customerName: invoice.customer.name,
    amount: invoice.totalAmount,
    currency: invoice.currency,
    status: invoice.status, // 'DRAFT', 'SENT', 'PAID', 'OVERDUE', 'CANCELLED'
    shareLink: `https://yaltopia.com/invoices/shared/${shareToken}`,
    expirationDate: "March 31, 2026",
    accessLimit: 5,
    customMessage: "Please review this invoice and let me know if you have any questions.",
    company: {
      name: "Yaltopia Ticket",
      logoUrl: "https://yaltopia.com/logo.png"
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/invoice-share`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  return response.json()
}

Send Enhanced Payment Request

Endpoint: POST /api/emails/enhanced-payment-request

When to use: When requesting payment with detailed line items and automatic status updates

async function sendEnhancedPaymentRequest(customer, paymentRequest) {
  const emailData = {
    to: customer.email,
    recipientName: customer.name,
    paymentRequestNumber: paymentRequest.number,
    amount: paymentRequest.totalAmount,
    currency: paymentRequest.currency,
    description: paymentRequest.description,
    dueDate: formatDate(paymentRequest.dueDate),
    lineItems: paymentRequest.items.map(item => ({
      description: item.description,
      quantity: item.quantity,
      unitPrice: item.unitPrice,
      total: item.quantity * item.unitPrice
    })),
    notes: "Please ensure payment is made by the due date to avoid late fees.",
    company: {
      name: "Yaltopia Ticket",
      bankDetails: {
        bankName: "Your Bank",
        accountName: "Yaltopia Ticket Ltd",
        accountNumber: "123456789",
        routingNumber: "021000021",
        referenceNote: `Payment for ${paymentRequest.number}`
      }
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/enhanced-payment-request`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  // Automatically update payment request status to "SENT"
  if (response.ok) {
    await updatePaymentRequestStatus(paymentRequest.id, 'SENT')
  }

  return response.json()
}

Send Payment Request

Endpoint: POST /api/emails/payment-request

When to use: When payment is due for tickets or services

async function sendPaymentRequest(customer, ticket, event) {
  const emailData = {
    to: customer.email,
    amount: ticket.totalPrice,
    currency: ticket.currency || "USD",
    description: `Ticket for ${event.name}`,
    dueDate: formatDate(ticket.paymentDueDate),
    company: {
      name: "Yaltopia Ticket",
      paymentLink: `https://yaltopia.com/pay/${ticket.id}`,
      bankDetails: {
        bankName: "Your Bank",
        accountName: "Yaltopia Ticket Ltd",
        accountNumber: "123456789"
      }
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/payment-request`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  return response.json()
}

Send Password Reset

Endpoint: POST /api/emails/password-reset

When to use: When user requests password reset

async function sendPasswordReset(user, resetToken) {
  const emailData = {
    to: user.email,
    resetLink: `https://yaltopia.com/reset-password?token=${resetToken}`,
    recipientName: user.firstName || user.name,
    company: {
      name: "Yaltopia Ticket",
      logoUrl: "https://yaltopia.com/logo.png"
    }
  }

  const response = await fetch(`${EMAIL_SERVICE_URL}/api/emails/password-reset`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(emailData)
  })

  return response.json()
}

🔧 Integration Patterns

1. Event-Driven Integration

// In your event handlers
eventEmitter.on('user.registered', async (user, event) => {
  await sendEventInvitation(user, event)
})

eventEmitter.on('payment.due', async (customer, ticket, event) => {
  await sendPaymentRequest(customer, ticket, event)
})

eventEmitter.on('password.reset.requested', async (user, resetToken) => {
  await sendPasswordReset(user, resetToken)
})

2. Direct Integration

// In your API endpoints
app.post('/api/events/:eventId/register', async (req, res) => {
  // Your registration logic
  const registration = await registerUserForEvent(userId, eventId)
  
  // Send invitation email
  await sendEventInvitation(user, event)
  
  res.json({ success: true, registration })
})

3. Background Job Integration

// Using a job queue (Bull, Agenda, etc.)
queue.add('send-invitation-email', {
  userId: user.id,
  eventId: event.id
})

queue.process('send-invitation-email', async (job) => {
  const { userId, eventId } = job.data
  const user = await getUserById(userId)
  const event = await getEventById(eventId)
  
  await sendEventInvitation(user, event)
})

📊 Response Format

All endpoints return consistent responses:

Success Response:

{
  "success": true,
  "messageId": "abc123-def456",
  "duration": "1.2s"
}

Error Response:

{
  "success": false,
  "error": "Validation error: Invalid email address",
  "code": "VALIDATION_ERROR"
}

🛡️ Error Handling

async function sendEmailSafely(emailData, endpoint) {
  try {
    const response = await fetch(`${EMAIL_SERVICE_URL}${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(emailData)
    })

    const result = await response.json()
    
    if (!result.success) {
      console.error('Email sending failed:', result.error)
      // Handle error (retry, log, notify admin, etc.)
      return false
    }

    console.log('Email sent successfully:', result.messageId)
    return true
    
  } catch (error) {
    console.error('Email service unreachable:', error)
    // Handle network errors
    return false
  }
}

🚀 Deployment Options

Option 1: Same Server

Deploy the email service on the same server as your Yaltopia backend:

const EMAIL_SERVICE_URL = 'http://localhost:3001'

Option 2: Separate Container

Deploy as a separate Docker container:

const EMAIL_SERVICE_URL = 'http://email-service:3001'

Option 3: Cloud Service

Deploy to a cloud platform:

const EMAIL_SERVICE_URL = 'https://your-email-service.herokuapp.com'

🔍 Health Check

Monitor the email service health:

async function checkEmailServiceHealth() {
  try {
    const response = await fetch(`${EMAIL_SERVICE_URL}/health`)
    const health = await response.json()
    return health.status === 'healthy'
  } catch (error) {
    return false
  }
}

📝 Best Practices

  1. Use environment variables for the email service URL
  2. Implement retry logic for failed email sends
  3. Log email sending attempts for debugging
  4. Handle errors gracefully - don't let email failures break your main flow
  5. Monitor email delivery through Resend dashboard
  6. Test with real email addresses in development

🎯 Example Integration

Here's a complete example of integrating with your user registration flow:

// In your Yaltopia backend
class EventService {
  constructor() {
    this.emailServiceUrl = process.env.EMAIL_SERVICE_URL || 'http://localhost:3001'
  }

  async registerUserForEvent(userId, eventId) {
    // Your existing registration logic
    const user = await User.findById(userId)
    const event = await Event.findById(eventId)
    const registration = await Registration.create({ userId, eventId })

    // Send invitation email
    try {
      await this.sendInvitationEmail(user, event)
    } catch (error) {
      console.error('Failed to send invitation email:', error)
      // Don't fail the registration if email fails
    }

    return registration
  }

  async sendInvitationEmail(user, event) {
    const emailData = {
      to: user.email,
      eventName: event.name,
      dateTime: this.formatEventDateTime(event.startDate),
      location: event.location,
      ctaUrl: `https://yaltopia.com/events/${event.id}/rsvp`,
      company: {
        name: "Yaltopia Ticket",
        logoUrl: "https://yaltopia.com/logo.png"
      }
    }

    const response = await fetch(`${this.emailServiceUrl}/api/emails/invitation`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(emailData)
    })

    if (!response.ok) {
      throw new Error(`Email service responded with ${response.status}`)
    }

    return response.json()
  }

  formatEventDateTime(startDate) {
    return new Date(startDate).toLocaleDateString('en-US', {
      weekday: 'long',
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    })
  }
}

This approach gives you beautiful, professional emails with minimal integration effort!