@warriorteam/redai-zalo-sdk
Version:
Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account, ZNS, Consultation Service, Group Messaging, and Social APIs
985 lines (793 loc) • 26.1 kB
Markdown
# 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.