# RedAI Zalo SDK - ZNS Service Guide

## Tổng quan

**Zalo Notification Service (ZNS)** là dịch vụ gửi tin nhắn thông báo chính thức từ doanh nghiệp đến khách hàng thông qua các template được duyệt trước. ZNS phù hợp cho:

- 📋 **Thông báo đơn hàng** - Xác nhận, cập nhật trạng thái đơn hàng
- 🎫 **Thông báo lịch hẹn** - Nhắc nhở cuộc hẹn, booking
- 💰 **Thông báo thanh toán** - Hóa đơn, biên lai, giao dịch
- 📢 **Thông báo khuyến mại** - Ưu đãi, chương trình đặc biệt
- ⚠️ **Cảnh báo bảo mật** - OTP, xác thực 2FA
- 📊 **Báo cáo định kỳ** - Báo cáo tài chính, thống kê

---

## Khởi tạo ZNS Service

```typescript
import { ZaloSDK } from "@warriorteam/redai-zalo-sdk";

const zalo = new ZaloSDK({
  appId: "your-oa-app-id",
  appSecret: "your-oa-app-secret",
  debug: true
});

// Access ZNS service
const znsService = zalo.zns;
```

---

## Gửi Tin nhắn ZNS

### 1. Gửi tin nhắn cơ bản

```typescript
// Gửi thông báo đơn hàng
const znsResponse = await zalo.zns.sendMessage(accessToken, {
  phone: "0123456789",              // Số điện thoại người nhận
  template_id: "your-template-id",  // ID template đã được duyệt
  template_data: {
    customer_name: "Nguyễn Văn An",
    order_id: "DH001234",
    total_amount: "500,000",
    delivery_date: "15/12/2024"
  },
  tracking_id: "tracking_001"       // ID để track (tùy chọn)
});

console.log("Message sent:", znsResponse.msg_id);
```

### 2. Gửi với chế độ development

```typescript
// Test trong môi trường development
const testResponse = await zalo.zns.sendMessage(accessToken, {
  phone: "0123456789",
  template_id: "template_id",
  template_data: {
    customer_name: "Test User",
    order_id: "TEST001"
  },
  mode: "development"  // Không trừ quota, chỉ test
});
```

### 3. Gửi tin nhắn có mã hóa (Hash Phone)

```typescript
// Hash phone number để bảo mật
import crypto from 'crypto';

function hashPhone(phone: string, secretKey: string): string {
  return crypto
    .createHmac('sha256', secretKey)
    .update(phone)
    .digest('hex');
}

const hashedPhone = hashPhone("0123456789", "your-secret-key");

const response = await zalo.zns.sendMessage(accessToken, {
  phone: hashedPhone,
  template_id: "template_id",
  template_data: { name: "User" },
  is_hash_phone: true  // Báo cho Zalo biết phone đã được hash
});
```

---

## Quản lý Templates

### 1. Lấy danh sách templates

```typescript
const templates = await zalo.zns.getTemplateList(accessToken, {
  offset: 0,
  limit: 20,
  status: 1  // 1: active, 2: pending, 3: rejected
});

console.log("Total templates:", templates.total);
templates.data.forEach(template => {
  console.log(`Template ID: ${template.templateId}`);
  console.log(`Name: ${template.templateName}`);
  console.log(`Status: ${template.status}`);
  console.log(`Content: ${template.previewContent}`);
});
```

### 2. Tạo template mới

```typescript
const newTemplate = await zalo.zns.createTemplate(accessToken, {
  templateName: "Xác nhận đơn hàng",
  templateContent: `Xin chào {{customer_name}},

Đơn hàng {{order_id}} của bạn đã được xác nhận.
Tổng tiền: {{total_amount}} VNĐ
Ngày giao hàng dự kiến: {{delivery_date}}

Cảm ơn bạn đã mua hàng!`,
  templateType: 3,  // 1: OTP, 2: Thông báo, 3: Xác nhận, 4: Khuyến mại
  timeout: 3600,    // Thời gian hiệu lực (seconds)
  previewData: JSON.stringify({
    customer_name: "Nguyễn Văn A",
    order_id: "DH001",
    total_amount: "299,000",
    delivery_date: "20/12/2024"
  }),
  hasHashPhone: false,  // Có hỗ trợ hash phone không
  hasAttachment: false  // Có đính kèm file không
});

console.log("Template created:", newTemplate.templateId);
console.log("Status:", newTemplate.status); // 2: pending approval
```

### 3. Cập nhật template

```typescript
const updatedTemplate = await zalo.zns.updateTemplate(accessToken, "template-id", {
  templateName: "Xác nhận đơn hàng - Updated",
  templateContent: `Kính chào {{customer_name}},

Đơn hàng #{{order_id}} đã được xác nhận.
Giá trị: {{total_amount}} VNĐ
Giao hàng: {{delivery_date}}

Hotline: 1800-xxx-xxx`,
  previewData: JSON.stringify({
    customer_name: "Khách hàng",
    order_id: "DH999",
    total_amount: "1,500,000",
    delivery_date: "25/12/2024"
  })
});
```

### 4. Kiểm tra trạng thái template

```typescript
const templateStatus = await zalo.zns.getTemplateStatus(accessToken, "template-id");

console.log("Template Status:", templateStatus.status);
// 1: Active (đã duyệt)
// 2: Pending (chờ duyệt) 
// 3: Rejected (bị từ chối)
// 4: Disabled (đã tắt)

if (templateStatus.status === 3) {
  console.log("Rejection reason:", templateStatus.rejectReason);
}
```

---

## Quản lý Quota

### 1. Kiểm tra quota hiện tại

```typescript
const quotaInfo = await zalo.zns.getQuotaInfo(accessToken);

console.log("Remaining quota:", quotaInfo.remainingQuota);
console.log("Daily quota:", quotaInfo.dailyQuota);
console.log("Monthly quota:", quotaInfo.monthlyQuota);

// Quota theo loại template
quotaInfo.quotaByType?.forEach(quota => {
  console.log(`Type ${quota.type}: ${quota.remaining}/${quota.total}`);
});
```

### 2. Kiểm tra quota trước khi gửi

```typescript
async function sendZNSWithQuotaCheck(
  accessToken: string, 
  request: ZNSMessageRequest
): Promise<boolean> {
  
  // Kiểm tra quota trước
  const quota = await zalo.zns.getQuotaInfo(accessToken);
  
  if (quota.remainingQuota <= 0) {
    console.log("⚠️ Không đủ quota để gửi tin nhắn");
    return false;
  }
  
  if (quota.remainingQuota < 10) {
    console.log(`⚠️ Cảnh báo: Chỉ còn ${quota.remainingQuota} quota`);
  }
  
  try {
    const result = await zalo.zns.sendMessage(accessToken, request);
    console.log(`✅ Gửi thành công. Quota còn lại: ${quota.remainingQuota - 1}`);
    return true;
  } catch (error) {
    console.error("❌ Gửi thất bại:", error);
    return false;
  }
}
```

---

## Template Data & Variables

### 1. Template Variables

Templates hỗ trợ các biến động với format `{{variable_name}}`:

```typescript
// Template content
const templateContent = `
Xin chào {{customer_name}},

Mã OTP của bạn là: {{otp_code}}
Mã này có hiệu lực trong {{validity_minutes}} phút.

Vui lòng không chia sẻ mã này với bất kỳ ai.
`;

// Template data
const templateData = {
  customer_name: "Nguyễn Văn A",
  otp_code: "123456",
  validity_minutes: "5"
};
```

### 2. Data Type Handling

```typescript
// Số và ngày tháng nên format thành string
const templateData = {
  // ✅ Đúng
  amount: "1,500,000",
  date: "25/12/2024",
  phone: "0123-456-789",
  
  // ❌ Sai - sẽ gây lỗi
  amount: 1500000,
  date: new Date(),
  phone: 123456789
};
```

### 3. Conditional Variables

```typescript
// Template với logic điều kiện
const templateContent = `
{{#if is_vip}}
Kính gửi Khách hàng VIP {{customer_name}},
{{else}}
Xin chào {{customer_name}},
{{/if}}

Đơn hàng {{order_id}} đã được {{status}}.

{{#if has_discount}}
Bạn được giảm {{discount_amount}} cho đơn hàng này.
{{/if}}

Cảm ơn bạn!
`;

const templateData = {
  customer_name: "Nguyễn Văn A",
  order_id: "DH001",
  status: "xác nhận",
  is_vip: true,
  has_discount: true,
  discount_amount: "50,000 VNĐ"
};
```

---

## Use Cases & Examples

### 1. E-commerce Order Notifications

```typescript
class OrderNotificationService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendOrderConfirmation(order: Order) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: order.customer.phone,
      template_id: "order_confirmation_template",
      template_data: {
        customer_name: order.customer.name,
        order_id: order.id,
        total_amount: this.formatCurrency(order.totalAmount),
        items_summary: this.getItemsSummary(order.items),
        delivery_date: this.formatDate(order.estimatedDelivery),
        tracking_url: `https://mystore.com/track/${order.id}`
      },
      tracking_id: `order_${order.id}`
    });
  }

  async sendShippingUpdate(order: Order, status: string) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: order.customer.phone,
      template_id: "shipping_update_template",
      template_data: {
        customer_name: order.customer.name,
        order_id: order.id,
        status: status,
        tracking_code: order.trackingCode,
        estimated_delivery: this.formatDate(order.estimatedDelivery)
      }
    });
  }

  private formatCurrency(amount: number): string {
    return new Intl.NumberFormat('vi-VN').format(amount) + ' VNĐ';
  }

  private formatDate(date: Date): string {
    return date.toLocaleDateString('vi-VN');
  }

  private getItemsSummary(items: OrderItem[]): string {
    return items.map(item => `${item.name} x${item.quantity}`).join(', ');
  }
}
```

### 2. Booking & Appointment System

```typescript
class BookingNotificationService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendBookingConfirmation(booking: Booking) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: booking.customer.phone,
      template_id: "booking_confirmation_template",
      template_data: {
        customer_name: booking.customer.name,
        service_name: booking.service.name,
        booking_date: this.formatDateTime(booking.appointmentTime),
        location: booking.location.address,
        staff_name: booking.staff.name,
        booking_id: booking.id,
        cancel_url: `https://mybusiness.com/cancel/${booking.id}`
      }
    });
  }

  async sendReminder(booking: Booking, hoursUntilAppointment: number) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: booking.customer.phone,
      template_id: "appointment_reminder_template",
      template_data: {
        customer_name: booking.customer.name,
        service_name: booking.service.name,
        appointment_time: this.formatDateTime(booking.appointmentTime),
        hours_until: hoursUntilAppointment.toString(),
        location: booking.location.address,
        contact_phone: "1800-xxx-xxx"
      }
    });
  }

  private formatDateTime(date: Date): string {
    return date.toLocaleString('vi-VN');
  }
}
```

### 3. Payment & Financial Notifications

```typescript
class PaymentNotificationService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendPaymentConfirmation(payment: Payment) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: payment.customer.phone,
      template_id: "payment_confirmation_template",
      template_data: {
        customer_name: payment.customer.name,
        amount: this.formatCurrency(payment.amount),
        payment_method: payment.method,
        transaction_id: payment.transactionId,
        payment_date: this.formatDateTime(payment.createdAt),
        merchant_name: payment.merchant.name,
        receipt_url: `https://pay.com/receipt/${payment.id}`
      }
    });
  }

  async sendInvoice(invoice: Invoice) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: invoice.customer.phone,
      template_id: "invoice_template",
      template_data: {
        customer_name: invoice.customer.name,
        invoice_number: invoice.number,
        amount: this.formatCurrency(invoice.totalAmount),
        due_date: this.formatDate(invoice.dueDate),
        payment_url: `https://pay.com/invoice/${invoice.id}`,
        company_name: invoice.company.name
      }
    });
  }
}
```

### 4. OTP & Security Messages

```typescript
class SecurityNotificationService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendOTP(phone: string, otpCode: string, purpose: string) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: phone,
      template_id: "otp_template",
      template_data: {
        otp_code: otpCode,
        purpose: purpose,
        validity_minutes: "5",
        app_name: "MyApp"
      }
    });
  }

  async sendSecurityAlert(user: User, activity: string, location: string) {
    return await this.zalo.zns.sendMessage(this.accessToken, {
      phone: user.phone,
      template_id: "security_alert_template",
      template_data: {
        customer_name: user.name,
        activity: activity,
        time: this.formatDateTime(new Date()),
        location: location,
        ip_address: this.getCurrentIP(),
        support_phone: "1800-xxx-xxx"
      }
    });
  }
}
```

---

## Batch Processing

### 1. Gửi tin nhắn hàng loạt

```typescript
class BatchZNSService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendBatchMessages(
    templateId: string,
    recipients: Array<{phone: string, data: Record<string, any>}>
  ) {
    const results = [];
    const batchSize = 10; // Gửi 10 tin mỗi lần để tránh rate limit
    
    for (let i = 0; i < recipients.length; i += batchSize) {
      const batch = recipients.slice(i, i + batchSize);
      
      const batchPromises = batch.map(async (recipient) => {
        try {
          const result = await this.zalo.zns.sendMessage(this.accessToken, {
            phone: recipient.phone,
            template_id: templateId,
            template_data: recipient.data,
            tracking_id: `batch_${Date.now()}_${i}`
          });
          
          return {
            phone: recipient.phone,
            success: true,
            messageId: result.msg_id
          };
        } catch (error) {
          return {
            phone: recipient.phone,
            success: false,
            error: error.message
          };
        }
      });
      
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
      
      // Delay giữa các batch để tránh rate limit
      if (i + batchSize < recipients.length) {
        await this.delay(1000); // 1 second delay
      }
    }
    
    return results;
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const batchService = new BatchZNSService(zalo, accessToken);

const recipients = [
  {
    phone: "0123456789",
    data: { name: "Nguyễn Văn A", code: "ABC123" }
  },
  {
    phone: "0987654321", 
    data: { name: "Trần Thị B", code: "XYZ789" }
  }
  // ... more recipients
];

const results = await batchService.sendBatchMessages("template_id", recipients);
console.log(`Sent ${results.filter(r => r.success).length}/${results.length} messages successfully`);
```

---

## Error Handling

### 1. Common ZNS Errors

```typescript
try {
  await zalo.zns.sendMessage(accessToken, request);
} catch (error) {
  switch (error.code) {
    case -216:
      console.error("Invalid access token");
      break;
    case -223:
      console.error("Quota exceeded");
      break;
    case -224:
      console.error("Template not found or not approved");
      break;
    case -225:
      console.error("Invalid phone number");
      break;
    case -226:
      console.error("Invalid template data");
      break;
    case -227:
      console.error("Template content mismatch");
      break;
    default:
      console.error("Unexpected error:", error.message);
  }
}
```

### 2. Retry Logic với Exponential Backoff

```typescript
class ZNSRetryService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async sendWithRetry(
    request: ZNSMessageRequest,
    maxRetries = 3
  ): Promise<any> {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await this.zalo.zns.sendMessage(this.accessToken, request);
      } catch (error) {
        lastError = error;
        
        // Không retry với một số lỗi cố định
        if (this.shouldNotRetry(error.code)) {
          throw error;
        }
        
        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    
    throw lastError;
  }

  private shouldNotRetry(errorCode: number): boolean {
    // Không retry với các lỗi sau
    const nonRetryableCodes = [-224, -225, -226, -227]; // Template/data errors
    return nonRetryableCodes.includes(errorCode);
  }
}
```

---

## Monitoring & Analytics

### 1. Message Tracking

```typescript
class ZNSAnalyticsService {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async trackMessageDelivery(trackingId: string) {
    // Implement your tracking logic
    const deliveryStatus = await this.getDeliveryStatus(trackingId);
    
    return {
      trackingId,
      status: deliveryStatus.status, // sent, delivered, failed
      sentAt: deliveryStatus.sentAt,
      deliveredAt: deliveryStatus.deliveredAt,
      failureReason: deliveryStatus.failureReason
    };
  }

  async generateQuotaReport(period: 'daily' | 'monthly') {
    const quota = await this.zalo.zns.getQuotaInfo(this.accessToken);
    
    return {
      period,
      totalQuota: quota.dailyQuota,
      usedQuota: quota.dailyQuota - quota.remainingQuota,
      remainingQuota: quota.remainingQuota,
      utilizationRate: ((quota.dailyQuota - quota.remainingQuota) / quota.dailyQuota * 100).toFixed(2) + '%'
    };
  }
}
```

### 2. Performance Monitoring

```typescript
class ZNSPerformanceMonitor {
  private metrics = {
    totalSent: 0,
    successful: 0,
    failed: 0,
    averageResponseTime: 0
  };

  async sendMessageWithMonitoring(
    zalo: ZaloSDK,
    accessToken: string,
    request: ZNSMessageRequest
  ) {
    const startTime = Date.now();
    
    try {
      const result = await zalo.zns.sendMessage(accessToken, request);
      
      this.metrics.totalSent++;
      this.metrics.successful++;
      this.updateResponseTime(Date.now() - startTime);
      
      console.log(`✅ Message sent successfully. Success rate: ${this.getSuccessRate()}%`);
      return result;
      
    } catch (error) {
      this.metrics.totalSent++;
      this.metrics.failed++;
      
      console.log(`❌ Message failed. Success rate: ${this.getSuccessRate()}%`);
      throw error;
    }
  }

  private getSuccessRate(): number {
    if (this.metrics.totalSent === 0) return 0;
    return (this.metrics.successful / this.metrics.totalSent * 100).toFixed(2);
  }

  private updateResponseTime(responseTime: number) {
    this.metrics.averageResponseTime = 
      (this.metrics.averageResponseTime * (this.metrics.successful - 1) + responseTime) / this.metrics.successful;
  }

  getMetrics() {
    return { ...this.metrics };
  }
}
```

---

## Best Practices

### 1. Template Design

```typescript
// ✅ Template tốt - rõ ràng, súc tích
const goodTemplate = `
Xin chào {{customer_name}},

Đơn hàng {{order_id}} đã được xác nhận thành công.
• Tổng tiền: {{total_amount}} VNĐ
• Ngày giao: {{delivery_date}}
• Mã vận đơn: {{tracking_code}}

Cảm ơn bạn đã tin tưởng!
Hotline: {{support_phone}}
`;

// ❌ Template kém - quá dài, không có cấu trúc
const badTemplate = `
Kính chào quý khách hàng {{customer_name}} của công ty chúng tôi. Chúng tôi xin thông báo rằng đơn hàng có mã số {{order_id}} của quý khách đã được xác nhận và đang trong quá trình xử lý...
`;
```

### 2. Phone Number Validation

```typescript
function validatePhoneNumber(phone: string): boolean {
  // Vietnamese phone number regex
  const phoneRegex = /^(0[3-9][0-9]{8})$/;
  return phoneRegex.test(phone.replace(/[\s\-\.]/g, ''));
}

function formatPhoneNumber(phone: string): string {
  // Remove all non-digits
  const cleaned = phone.replace(/\D/g, '');
  
  // Add country code if missing
  if (cleaned.length === 10 && cleaned.startsWith('0')) {
    return cleaned;
  } else if (cleaned.length === 9) {
    return '0' + cleaned;
  }
  
  throw new Error('Invalid phone number format');
}
```

### 3. Template Data Validation

```typescript
function validateTemplateData(template: string, data: Record<string, any>): boolean {
  // Extract variables from template
  const variables = template.match(/\{\{(\w+)\}\}/g)?.map(match => 
    match.replace(/\{\{|\}\}/g, '')
  ) || [];
  
  // Check if all variables are provided
  const missingVars = variables.filter(variable => !(variable in data));
  
  if (missingVars.length > 0) {
    throw new Error(`Missing template variables: ${missingVars.join(', ')}`);
  }
  
  // Validate data types (all should be strings)
  const invalidTypes = Object.entries(data)
    .filter(([key, value]) => typeof value !== 'string')
    .map(([key]) => key);
  
  if (invalidTypes.length > 0) {
    throw new Error(`Invalid data types for: ${invalidTypes.join(', ')}. All values must be strings.`);
  }
  
  return true;
}
```

---

## Testing ZNS

### 1. Development Mode Testing

```typescript
// Test template trong development mode
async function testTemplate(templateId: string) {
  const testData = {
    customer_name: "Nguyễn Test",
    order_id: "TEST001",
    total_amount: "100,000"
  };
  
  try {
    const result = await zalo.zns.sendMessage(accessToken, {
      phone: "0123456789", // Test phone number
      template_id: templateId,
      template_data: testData,
      mode: "development" // Không trừ quota
    });
    
    console.log("✅ Template test successful:", result.msg_id);
    return true;
  } catch (error) {
    console.error("❌ Template test failed:", error.message);
    return false;
  }
}
```

### 2. Unit Tests

```typescript
// zns.test.ts
import { ZaloSDK } from '@warriorteam/redai-zalo-sdk';

describe('ZNS Service', () => {
  const zalo = new ZaloSDK({
    appId: 'test_app_id',
    appSecret: 'test_app_secret'
  });

  it('should send ZNS message successfully', async () => {
    const mockResponse = {
      error: 0,
      message: "Success",
      data: { msg_id: "test_msg_id" }
    };

    // Mock the API call
    jest.spyOn(zalo.zns, 'sendMessage').mockResolvedValue(mockResponse);

    const result = await zalo.zns.sendMessage('test_token', {
      phone: '0123456789',
      template_id: 'test_template',
      template_data: { name: 'Test User' }
    });

    expect(result.msg_id).toBe('test_msg_id');
  });
});
```

---

## Production Checklist

### 1. Trước khi Go Live

- [ ] Templates đã được Zalo duyệt
- [ ] Test tất cả templates trong development mode
- [ ] Setup monitoring và logging
- [ ] Implement error handling và retry logic
- [ ] Validate phone numbers trước khi gửi
- [ ] Setup quota monitoring và alerts
- [ ] Backup và recovery plan
- [ ] Performance testing với volume cao

### 2. Monitoring Production

```typescript
// Production monitoring setup
class ProductionZNSMonitor {
  constructor(private zalo: ZaloSDK, private accessToken: string) {}

  async healthCheck(): Promise<boolean> {
    try {
      const quota = await this.zalo.zns.getQuotaInfo(this.accessToken);
      
      // Alert nếu quota thấp
      if (quota.remainingQuota < 100) {
        this.sendAlert(`⚠️ ZNS Quota thấp: ${quota.remainingQuota} còn lại`);
      }
      
      return true;
    } catch (error) {
      this.sendAlert(`❌ ZNS Health check failed: ${error.message}`);
      return false;
    }
  }

  private sendAlert(message: string) {
    // Send to your monitoring system (Slack, email, etc.)
    console.error(message);
  }
}
```

---

## Troubleshooting

### 1. Common Issues

**Q: Template không gửi được**
```
A: Kiểm tra:
   - Template đã được duyệt chưa (status = 1)
   - Template data có đúng format không
   - Phone number có hợp lệ không
   - Còn quota không
```

**Q: "Template content mismatch" error**
```
A: Template data phải khớp chính xác với template content
   - Tên biến phải giống nhau
   - Không được thiếu biến
   - Tất cả giá trị phải là string
```

### 2. Debug ZNS Issues

```typescript
async function debugZNSMessage(
  zalo: ZaloSDK,
  accessToken: string,
  request: ZNSMessageRequest
) {
  console.log("🔍 Debug ZNS Request:");
  console.log("Phone:", request.phone);
  console.log("Template ID:", request.template_id);
  console.log("Template Data:", JSON.stringify(request.template_data, null, 2));
  
  // Check quota first
  try {
    const quota = await zalo.zns.getQuotaInfo(accessToken);
    console.log("📊 Quota Info:", quota);
  } catch (error) {
    console.error("❌ Failed to get quota:", error);
  }
  
  // Check template status
  try {
    const template = await zalo.zns.getTemplateStatus(accessToken, request.template_id);
    console.log("📝 Template Status:", template);
  } catch (error) {
    console.error("❌ Failed to get template status:", error);
  }
  
  // Attempt to send
  try {
    const result = await zalo.zns.sendMessage(accessToken, request);
    console.log("✅ Message sent successfully:", result);
    return result;
  } catch (error) {
    console.error("❌ Failed to send message:", error);
    throw error;
  }
}
```

---

## Next Steps

Sau khi làm chủ ZNS Service:

1. **[Message Services](./MESSAGE_SERVICES.md)** - Tìm hiểu các loại tin nhắn khác
2. **[Webhook Events](./WEBHOOK_EVENTS.md)** - Xử lý ZNS delivery events
3. **[User Management](./USER_MANAGEMENT.md)** - Quản lý danh sách khách hàng
4. **[Error Handling](./ERROR_HANDLING.md)** - Xử lý lỗi toàn diện

Tham khảo **[API Reference](./API_REFERENCE.md)** để biết chi tiết về tất cả ZNS methods.