vercel-production.md•14.4 kB
# BaaS SMS/MMS Vercel Production Deployment
Complete guide for deploying BaaS SMS/MMS integration on Vercel.
## Prerequisites
- Vercel account
- BaaS API key and project ID
- Node.js project with Next.js (recommended)
## Environment Configuration
### 1. Environment Variables
Add these environment variables in Vercel Dashboard:
```bash
# Vercel Dashboard > Settings > Environment Variables
BAAS_API_KEY=your-api-key-here
BAAS_BASE_URL=https://api.aiapp.link
```
### 2. vercel.json Configuration
```json
{
"env": {
"BAAS_API_KEY": "@baas-api-key",
"BAAS_BASE_URL": "https://api.aiapp.link"
},
"build": {
"env": {
"BAAS_API_KEY": "@baas-api-key"
}
},
"functions": {
"pages/api/**/*.js": {
"maxDuration": 30
}
}
}
```
## API Routes Implementation
### pages/api/sms/send.js
```javascript
import { BaaSMessageService } from '../../../lib/baas-sms-service';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { recipients, message, callbackNumber } = req.body;
// Validate input
if (!recipients || !Array.isArray(recipients) || recipients.length === 0) {
return res.status(400).json({ error: 'Recipients are required' });
}
if (!message || message.length > 2000) {
return res.status(400).json({ error: 'Invalid message' });
}
// Initialize service
const service = new BaaSMessageService(
process.env.BAAS_API_KEY
);
// Send SMS
const result = await service.sendSMS(recipients, message, callbackNumber);
if (result.success) {
res.status(200).json({
success: true,
groupId: result.groupId,
message: 'SMS sent successfully'
});
} else {
res.status(400).json({
success: false,
error: result.error,
errorCode: result.errorCode
});
}
} catch (error) {
console.error('SMS API Error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
}
```
### pages/api/mms/send.js
```javascript
import { BaaSMessageService } from '../../../lib/baas-sms-service';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { recipients, message, subject, callbackNumber, imageUrls } = req.body;
// Validate input
if (!recipients || !Array.isArray(recipients) || recipients.length === 0) {
return res.status(400).json({ error: 'Recipients are required' });
}
if (!message || message.length > 2000) {
return res.status(400).json({ error: 'Invalid message' });
}
if (!subject || subject.length > 40) {
return res.status(400).json({ error: 'Invalid subject' });
}
if (imageUrls && imageUrls.length > 5) {
return res.status(400).json({ error: 'Maximum 5 images allowed' });
}
// Initialize service
const service = new BaaSMessageService(
process.env.BAAS_API_KEY
);
// Send MMS
const result = await service.sendMMS(recipients, message, subject, callbackNumber, imageUrls);
if (result.success) {
res.status(200).json({
success: true,
groupId: result.groupId,
message: 'MMS sent successfully'
});
} else {
res.status(400).json({
success: false,
error: result.error,
errorCode: result.errorCode
});
}
} catch (error) {
console.error('MMS API Error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
}
```
### pages/api/sms/status/[groupId].js
```javascript
import { BaaSMessageService } from '../../../../lib/baas-sms-service';
export default async function handler(req, res) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { groupId } = req.query;
if (!groupId || isNaN(parseInt(groupId))) {
return res.status(400).json({ error: 'Invalid group ID' });
}
// Initialize service
const service = new BaaSMessageService(
process.env.BAAS_API_KEY
);
// Check status
const result = await service.getMessageStatus(parseInt(groupId));
if (result.success !== false) {
res.status(200).json(result);
} else {
res.status(400).json({
success: false,
error: result.error
});
}
} catch (error) {
console.error('Status API Error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
}
```
## Frontend Integration
### components/SMSForm.js
```javascript
import { useState } from 'react';
export default function SMSForm() {
const [formData, setFormData] = useState({
phoneNumber: '',
message: '',
callbackNumber: '02-1234-5678'
});
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setResult(null);
try {
const recipients = [
{
phone_number: formData.phoneNumber,
member_code: 'web_user'
}
];
const response = await fetch('/api/sms/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipients,
message: formData.message,
callbackNumber: formData.callbackNumber
}),
});
const data = await response.json();
setResult(data);
if (data.success) {
setFormData({ ...formData, phoneNumber: '', message: '' });
}
} catch (error) {
setResult({
success: false,
error: 'Network error occurred'
});
} finally {
setLoading(false);
}
};
return (
<div className="max-w-md mx-auto bg-white p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-4">SMS 전송</h2>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-sm font-medium mb-2">
전화번호
</label>
<input
type="tel"
value={formData.phoneNumber}
onChange={(e) => setFormData({ ...formData, phoneNumber: e.target.value })}
placeholder="010-1234-5678"
className="w-full p-2 border rounded-md"
required
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-2">
메시지
</label>
<textarea
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
placeholder="전송할 메시지를 입력하세요"
maxLength={2000}
rows={4}
className="w-full p-2 border rounded-md"
required
/>
<div className="text-sm text-gray-500 mt-1">
{formData.message.length}/2000
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-2">
발신번호
</label>
<input
type="tel"
value={formData.callbackNumber}
onChange={(e) => setFormData({ ...formData, callbackNumber: e.target.value })}
className="w-full p-2 border rounded-md"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600 disabled:opacity-50"
>
{loading ? '전송 중...' : 'SMS 전송'}
</button>
</form>
{result && (
<div className={`mt-4 p-3 rounded-md ${
result.success ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
}`}>
{result.success ? (
<div>
<div>✅ SMS가 성공적으로 전송되었습니다!</div>
<div className="text-sm mt-1">Group ID: {result.groupId}</div>
</div>
) : (
<div>❌ {result.error}</div>
)}
</div>
)}
</div>
);
}
```
## Deployment Steps
### 1. Install Dependencies
```bash
npm install
# or
yarn install
```
### 2. Environment Variables Setup
```bash
# Add to Vercel Dashboard > Settings > Environment Variables
BAAS_API_KEY=your-actual-api-key
```
### 3. Deploy to Vercel
```bash
# Using Vercel CLI
npm i -g vercel
vercel
# Or connect GitHub repository in Vercel Dashboard
```
### 4. Test Deployment
```bash
curl -X POST https://your-app.vercel.app/api/sms/send \
-H "Content-Type: application/json" \
-d '{
"recipients": [{"phone_number": "010-1234-5678", "member_code": "test"}],
"message": "Test message",
"callbackNumber": "02-1234-5678"
}'
```
## Edge Runtime Compatibility
### pages/api/sms/send-edge.js (Edge Runtime)
```javascript
export const config = {
runtime: 'edge',
}
export default async function handler(req) {
if (req.method !== 'POST') {
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
status: 405,
headers: { 'Content-Type': 'application/json' }
});
}
try {
const body = await req.json();
const { recipients, message, callbackNumber } = body;
// Validate input
if (!recipients || !Array.isArray(recipients) || recipients.length === 0) {
return new Response(JSON.stringify({ error: 'Recipients are required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Make direct API call (Edge Runtime compatible)
const response = await fetch('https://api.aiapp.link/api/message/sms', {
method: 'POST',
headers: {
'X-API-KEY': process.env.BAAS_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipients,
message,
callback_number: callbackNumber,
channel_id: 1
})
});
const result = await response.json();
if (response.ok && result.success) {
return new Response(JSON.stringify({
success: true,
groupId: result.data.group_id,
message: 'SMS sent successfully'
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} else {
return new Response(JSON.stringify({
success: false,
error: result.message || 'Send failed',
errorCode: result.error_code
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: 'Internal server error'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
```
## Monitoring and Logging
### lib/logger.js
```javascript
export class Logger {
static log(level, message, meta = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
...meta
};
// In production, use a logging service like LogRocket, Sentry, etc.
if (process.env.NODE_ENV === 'production') {
console.log(JSON.stringify(logEntry));
} else {
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, meta);
}
}
static info(message, meta = {}) {
this.log('info', message, meta);
}
static error(message, meta = {}) {
this.log('error', message, meta);
}
static warn(message, meta = {}) {
this.log('warn', message, meta);
}
}
```
## Security Best Practices
### 1. Rate Limiting
```javascript
// lib/rate-limit.js
const rateLimitMap = new Map();
export function rateLimit(ip, maxRequests = 10, windowMs = 60000) {
const now = Date.now();
const windowStart = now - windowMs;
if (!rateLimitMap.has(ip)) {
rateLimitMap.set(ip, []);
}
const requests = rateLimitMap.get(ip);
// Remove old requests
const validRequests = requests.filter(time => time > windowStart);
if (validRequests.length >= maxRequests) {
return false;
}
validRequests.push(now);
rateLimitMap.set(ip, validRequests);
return true;
}
```
### 2. Input Validation
```javascript
// lib/validation.js
export function validatePhoneNumber(phoneNumber) {
const pattern = /^010-\d{4}-\d{4}$/;
return pattern.test(phoneNumber);
}
export function validateMessage(message) {
return message && typeof message === 'string' && message.length <= 2000;
}
export function sanitizeInput(input) {
if (typeof input !== 'string') return input;
return input.trim().replace(/[<>]/g, '');
}
```
## Troubleshooting
### Common Issues
1. **Environment Variables Not Loading**
- Check Vercel Dashboard environment variable spelling
- Ensure variables are set for correct environment (Production/Preview/Development)
2. **Function Timeout**
- Increase timeout in vercel.json
- Optimize API calls to BaaS service
3. **CORS Issues**
- Add proper CORS headers to API routes
- Configure allowed origins
4. **Build Failures**
- Check Node.js version compatibility
- Verify all dependencies are listed in package.json
## Performance Optimization
### 1. Caching Strategy
```javascript
// lib/cache.js
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export function getCached(key) {
const item = cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
cache.delete(key);
return null;
}
return item.data;
}
export function setCache(key, data, ttl = CACHE_TTL) {
cache.set(key, {
data,
expiry: Date.now() + ttl
});
}
```
### 2. Bundle Optimization
```javascript
// next.config.js
module.exports = {
webpack: (config) => {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
};
return config;
},
};
```
This deployment guide ensures your BaaS SMS/MMS integration runs smoothly on Vercel with proper error handling, monitoring, and security measures.