448 lines
12 KiB
Markdown
448 lines
12 KiB
Markdown
# 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
|
|
```bash
|
|
# Using Docker (recommended)
|
|
docker-compose up -d
|
|
|
|
# Or Node.js
|
|
npm run build && npm start
|
|
```
|
|
|
|
### 2. Configure Environment
|
|
```env
|
|
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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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**:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"messageId": "abc123-def456",
|
|
"duration": "1.2s"
|
|
}
|
|
```
|
|
|
|
**Error Response**:
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Validation error: Invalid email address",
|
|
"code": "VALIDATION_ERROR"
|
|
}
|
|
```
|
|
|
|
## 🛡️ Error Handling
|
|
|
|
```javascript
|
|
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:
|
|
```javascript
|
|
const EMAIL_SERVICE_URL = 'http://localhost:3001'
|
|
```
|
|
|
|
### Option 2: Separate Container
|
|
Deploy as a separate Docker container:
|
|
```javascript
|
|
const EMAIL_SERVICE_URL = 'http://email-service:3001'
|
|
```
|
|
|
|
### Option 3: Cloud Service
|
|
Deploy to a cloud platform:
|
|
```javascript
|
|
const EMAIL_SERVICE_URL = 'https://your-email-service.herokuapp.com'
|
|
```
|
|
|
|
## 🔍 Health Check
|
|
|
|
Monitor the email service health:
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
// 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! |