# Stack0 > AI-native infrastructure platform for modern applications. Production-ready email, CDN, and image processing. ## What is Stack0? Stack0 provides essential infrastructure services for developers building web applications: - **Email Service**: Send transactional and marketing emails with templates, tracking, and analytics - **CDN**: Asset storage and delivery with on-the-fly image transformations - **Domain Management**: Custom domain verification and DNS management All services are usage-based with no monthly minimums. Get started with $5 in free credits. ## Quick Start ### 1. Create an Account Sign up at https://app.stack0.dev and create your first organization. ### 2. Get Your API Key Navigate to Settings → API Keys in your dashboard and generate a new API key. ### 3. Install the SDK ```bash npm install @stack0/sdk # or bun add @stack0/sdk ``` ### 4. Send Your First Email ```typescript import { Stack0 } from '@stack0/sdk'; const stack0 = new Stack0({ apiKey: process.env.STACK0_API_KEY, }); await stack0.mail.send({ from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Welcome to our app!', html: '

Thanks for signing up!

', }); ``` ### 5. Upload Your First Asset ```typescript const asset = await stack0.cdn.upload({ projectSlug: 'my-project', file: imageBuffer, filename: 'hero.jpg', mimeType: 'image/jpeg', }); console.log(asset.cdnUrl); // https://cdn.stack0.dev/... ``` ## CDN API Reference The CDN API enables you to upload, manage, and transform assets. All CDN operations are project-scoped using `projectSlug`. ### Upload File Upload files directly with the high-level `upload` method: ```typescript const asset = await stack0.cdn.upload({ projectSlug: 'my-project', file: fileBuffer, // Blob, Buffer, or ArrayBuffer filename: 'photo.jpg', mimeType: 'image/jpeg', folder: '/images/avatars', // Optional folder path metadata: { userId: 'user_123' }, // Optional custom metadata }); // Response { id: 'asset_abc123', filename: 'photo.jpg', originalFilename: 'photo.jpg', mimeType: 'image/jpeg', size: 102400, type: 'image', cdnUrl: 'https://cdn.stack0.dev/...', width: 1920, height: 1080, status: 'ready', folder: '/images/avatars', createdAt: Date } ``` #### Manual Upload Flow For more control, use the presigned URL flow: ```typescript // 1. Get presigned upload URL const { uploadUrl, assetId, expiresAt } = await stack0.cdn.getUploadUrl({ projectSlug: 'my-project', filename: 'document.pdf', mimeType: 'application/pdf', size: 1048576, // File size in bytes folder: '/documents', }); // 2. Upload directly to S3 await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'application/pdf' }, }); // 3. Confirm upload completed const asset = await stack0.cdn.confirmUpload(assetId); ``` ### List Assets Query assets with filters and pagination: ```typescript const { assets, total, hasMore } = await stack0.cdn.list({ projectSlug: 'my-project', type: 'image', // 'image' | 'video' | 'audio' | 'document' | 'other' status: 'ready', // 'pending' | 'processing' | 'ready' | 'failed' folder: '/images', search: 'avatar', // Search in filename tags: ['profile', 'user'], sortBy: 'createdAt', // 'createdAt' | 'filename' | 'size' | 'type' sortOrder: 'desc', // 'asc' | 'desc' limit: 20, offset: 0, }); ``` ### Get Asset Retrieve a single asset by ID: ```typescript const asset = await stack0.cdn.get('asset_abc123'); console.log(asset.filename); console.log(asset.cdnUrl); console.log(asset.width, asset.height); // For images/videos console.log(asset.duration); // For videos/audio (seconds) ``` ### Update Asset Update asset metadata: ```typescript const asset = await stack0.cdn.update({ id: 'asset_abc123', filename: 'renamed-photo.jpg', folder: '/images/archived', tags: ['nature', 'sunset'], alt: 'A beautiful sunset over the ocean', metadata: { category: 'landscape' }, }); ``` ### Delete Assets ```typescript // Delete single asset await stack0.cdn.delete('asset_abc123'); // Delete multiple assets const { deletedCount } = await stack0.cdn.deleteMany([ 'asset_abc123', 'asset_def456', 'asset_ghi789', ]); ``` ### Move Assets Move assets to a different folder: ```typescript await stack0.cdn.move({ assetIds: ['asset_abc123', 'asset_def456'], folder: '/images/archive', // Use null for root folder }); ``` ### Image Transformations Get optimized and transformed image URLs on-the-fly: ```typescript const { url, originalUrl } = await stack0.cdn.getTransformUrl({ assetId: 'asset_abc123', options: { width: 800, height: 600, fit: 'cover', // 'cover' | 'contain' | 'fill' | 'inside' | 'outside' format: 'webp', // 'webp' | 'jpeg' | 'png' | 'avif' quality: 80, // 1-100 }, }); // Use transformed URL // Optimized image ``` #### Transformation Options - `width`: Target width in pixels - `height`: Target height in pixels - `fit`: How to fit the image - `cover`: Crop to fill dimensions (default) - `contain`: Fit within dimensions, preserving aspect ratio - `fill`: Stretch to fill dimensions - `inside`: Fit inside dimensions - `outside`: Cover dimensions while preserving aspect ratio - `format`: Output format (webp recommended for web) - `quality`: Compression quality (1-100) ### Folders Organize assets with virtual folders: ```typescript // Get folder tree const tree = await stack0.cdn.getFolderTree({ projectSlug: 'my-project', maxDepth: 3, }); // Tree structure [ { id: 'folder_123', name: 'images', path: '/images', assetCount: 42, children: [ { id: 'folder_456', name: 'avatars', path: '/images/avatars', assetCount: 15, children: [] } ] } ] // Create folder const folder = await stack0.cdn.createFolder({ projectSlug: 'my-project', name: 'avatars', parentId: 'folder_123', // Optional, null for root }); // Delete folder await stack0.cdn.deleteFolder('folder_123'); await stack0.cdn.deleteFolder('folder_123', true); // Delete with contents ``` ### Asset Types Assets are automatically categorized by MIME type: - `image`: jpeg, png, gif, webp, svg, etc. - `video`: mp4, webm, mov, avi, etc. - `audio`: mp3, wav, ogg, etc. - `document`: pdf, doc, docx, txt, etc. - `other`: All other file types ### Asset Status Flow 1. **pending**: Upload URL generated, waiting for upload 2. **processing**: File uploaded, being processed (thumbnails, metadata extraction) 3. **ready**: Asset ready for use 4. **failed**: Processing failed ### CDN URL Structure Assets are served from the CDN with the following URL patterns: - Original: `https://cdn.stack0.dev/{s3Key}` - Transformed: `https://cdn.stack0.dev/{s3Key}?w=800&h=600&fit=cover&f=webp&q=80` ## Mail API Reference ### Send Email Send a single email to one or more recipients. ```typescript const result = await stack0.mail.send({ from: 'noreply@yourdomain.com', to: 'user@example.com', subject: 'Hello World', html: '

Email content here

', text: 'Email content here', // Optional plaintext version }); // Response { id: 'email_123abc', from: 'noreply@yourdomain.com', to: 'user@example.com', subject: 'Hello World', status: 'pending', createdAt: Date } ``` #### Parameters - `from` (required): Sender email address (string or `{ email: string, name?: string }`) - `to` (required): Recipient(s) - single email or array - `subject` (required): Email subject line - `html`: HTML email body - `text`: Plain text email body - `cc`: Carbon copy recipients - `bcc`: Blind carbon copy recipients - `replyTo`: Reply-to address - `templateId`: Use a saved template (see Templates below) - `templateVariables`: Variables to substitute in template - `tags`: Array of strings for categorization - `metadata`: Custom key-value data for your records - `attachments`: Array of file attachments - `headers`: Custom email headers - `scheduledAt`: Schedule for future delivery ### Get Email Status Retrieve the status and details of a sent email. ```typescript const email = await stack0.mail.get('email_123abc'); // Response includes: { id: string, from: string, to: string, subject: string, status: 'pending' | 'sent' | 'delivered' | 'bounced' | 'failed', html: string | null, text: string | null, createdAt: Date, sentAt: Date | null, deliveredAt: Date | null, openedAt: Date | null, clickedAt: Date | null, bouncedAt: Date | null, } ``` ### Email Status Flow 1. **pending**: Email created, queued for sending 2. **sent**: Email handed off to mail server 3. **delivered**: Email successfully delivered to recipient 4. **bounced**: Email rejected by recipient server 5. **failed**: Permanent delivery failure ### Using Templates Create reusable email templates in your dashboard with variable substitution. #### 1. Create Template In the dashboard, go to Mail → Templates → Create Template: - **Name**: Password Reset - **Subject**: Reset your password - **Content**: ```html

Hi {{name}},

Click here to reset your password:

Reset Password ``` #### 2. Send with Template ```typescript await stack0.mail.send({ from: 'noreply@yourdomain.com', to: 'user@example.com', templateId: 'tpl_abc123', templateVariables: { name: 'John Doe', resetUrl: 'https://yourapp.com/reset?token=xyz', }, }); ``` Variables use `{{variableName}}` syntax and are replaced before sending. ### Attachments Send files with your emails: ```typescript await stack0.mail.send({ from: 'noreply@yourdomain.com', to: 'user@example.com', subject: 'Your invoice', html: '

Please find your invoice attached.

', attachments: [ { filename: 'invoice.pdf', content: base64EncodedContent, contentType: 'application/pdf', }, { filename: 'logo.png', path: 'https://yourdomain.com/logo.png', // Or use URL }, ], }); ``` ### Batch Sending Send multiple unique emails in one API call: ```typescript await stack0.mail.sendBatch([ { from: 'noreply@yourdomain.com', to: 'user1@example.com', subject: 'Welcome John!', html: '

Hi John!

', }, { from: 'noreply@yourdomain.com', to: 'user2@example.com', subject: 'Welcome Jane!', html: '

Hi Jane!

', }, ]); ``` ### Broadcast Emails Send the same email to multiple recipients: ```typescript await stack0.mail.sendBroadcast({ from: 'newsletter@yourdomain.com', to: ['user1@example.com', 'user2@example.com', 'user3@example.com'], subject: 'Monthly Newsletter', html: '

Newsletter content

', }); ``` ## Domain Verification To send emails from your custom domain, you need to verify domain ownership. ### 1. Add Domain In the dashboard, go to Mail → Domains → Add Domain and enter your domain (e.g., `yourdomain.com`). ### 2. Configure DNS Records Add the provided DNS records to your domain: - **SPF Record**: Authorizes Stack0 to send emails - **DKIM Records**: Cryptographic authentication - **DMARC Record**: Email policy and reporting - **Verification Record**: Proves domain ownership ### 3. Verify Domain Click "Verify Domain" in the dashboard. Verification typically completes within minutes but can take up to 72 hours. ### 4. Set as Default Mark your verified domain as default to use it automatically for all emails. ## Email Tracking Stack0 automatically tracks email engagement: - **Sends**: When email leaves the server - **Deliveries**: When recipient server accepts email - **Opens**: When recipient opens email (requires HTML) - **Clicks**: When recipient clicks links in email - **Bounces**: When email is rejected - **Complaints**: When recipient marks as spam View analytics in the dashboard under Mail → Analytics. ## Common Use Cases ### Transactional Emails Send emails triggered by user actions: ```typescript // Welcome email await stack0.mail.send({ from: 'welcome@yourapp.com', to: newUser.email, subject: 'Welcome to YourApp!', templateId: 'welcome-email', templateVariables: { name: newUser.name }, }); // Password reset await stack0.mail.send({ from: 'noreply@yourapp.com', to: user.email, subject: 'Reset your password', templateId: 'password-reset', templateVariables: { resetUrl: generateResetUrl(user.id), expiresIn: '1 hour', }, }); // Order confirmation await stack0.mail.send({ from: 'orders@yourapp.com', to: customer.email, subject: `Order #${order.id} confirmed`, templateId: 'order-confirmation', templateVariables: { order, customer }, }); ``` ### Marketing Emails Send newsletters and promotional content: ```typescript // Newsletter to subscribers const subscribers = await getActiveSubscribers(); await stack0.mail.sendBroadcast({ from: 'newsletter@yourapp.com', to: subscribers.map(s => s.email), subject: 'Monthly Update - October 2025', templateId: 'monthly-newsletter', tags: ['newsletter', 'october-2025'], }); // Product announcement await stack0.mail.sendBroadcast({ from: 'updates@yourapp.com', to: userSegment.emails, subject: 'Introducing Our New Feature!', html: announcementHtml, tags: ['product-update', 'feature-launch'], }); ``` ### User-Generated Content with CDN Handle user uploads in your application: ```typescript // Upload user avatar const avatar = await stack0.cdn.upload({ projectSlug: 'my-project', file: uploadedFile, filename: `avatar-${userId}.jpg`, mimeType: 'image/jpeg', folder: '/avatars', metadata: { userId }, }); // Get optimized thumbnail const { url: thumbnailUrl } = await stack0.cdn.getTransformUrl({ assetId: avatar.id, options: { width: 150, height: 150, fit: 'cover', format: 'webp' }, }); // Save to user profile await updateUser(userId, { avatarUrl: thumbnailUrl }); ``` ### Product Images Manage e-commerce product images: ```typescript // Upload product image const image = await stack0.cdn.upload({ projectSlug: 'my-project', file: productImage, filename: `product-${productId}-main.jpg`, mimeType: 'image/jpeg', folder: `/products/${productId}`, tags: ['product', productId], metadata: { productId, isPrimary: true }, }); // Generate multiple sizes for responsive images const sizes = [ { width: 1200, name: 'large' }, { width: 800, name: 'medium' }, { width: 400, name: 'small' }, { width: 150, name: 'thumbnail' }, ]; const urls = await Promise.all( sizes.map(async ({ width, name }) => { const { url } = await stack0.cdn.getTransformUrl({ assetId: image.id, options: { width, format: 'webp', quality: 85 }, }); return { name, url }; }) ); ``` ## Error Handling The SDK throws typed errors for different scenarios: ```typescript import { Stack0, Stack0Error } from '@stack0/sdk'; try { await stack0.mail.send({ from: 'invalid@unverified-domain.com', to: 'user@example.com', subject: 'Test', html: '

Test

', }); } catch (error) { if (error instanceof Stack0Error) { console.error('Stack0 Error:', error.message); console.error('Status Code:', error.statusCode); console.error('Error Code:', error.code); } } ``` Common error codes: - `INVALID_FROM`: From address not verified - `INVALID_EMAIL`: Malformed email address - `RATE_LIMIT`: Too many requests - `QUOTA_EXCEEDED`: Usage limit reached - `UNAUTHORIZED`: Invalid API key - `NOT_FOUND`: Resource not found - `PROJECT_NOT_FOUND`: Invalid project slug ## Rate Limits Default rate limits per organization: - **Emails per second**: 10 - **Emails per minute**: 100 - **Emails per hour**: 1,000 - **Emails per day**: 10,000 - **CDN uploads per minute**: 60 - **CDN requests per second**: 100 Contact support for higher limits. Rate limit information is included in response headers: ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1637000000 ``` ## Webhooks Configure webhooks to receive real-time notifications about email events. ### Available Events - `email.sent`: Email sent to mail server - `email.delivered`: Email delivered to recipient - `email.bounced`: Email bounced - `email.opened`: Recipient opened email - `email.clicked`: Recipient clicked link - `email.complained`: Recipient marked as spam ### Setup 1. Go to Mail → Webhooks → Create Webhook 2. Enter your endpoint URL 3. Select events to receive 4. Save and copy the signing secret ### Payload Example ```json { "event": "email.delivered", "timestamp": "2025-11-24T10:30:00Z", "data": { "id": "email_123abc", "from": "noreply@yourdomain.com", "to": "user@example.com", "subject": "Welcome!", "status": "delivered", "deliveredAt": "2025-11-24T10:30:00Z" } } ``` ### Verifying Webhooks Webhooks include a signature header for verification: ```typescript import { createHmac } from 'crypto'; function verifyWebhook(payload: string, signature: string, secret: string) { const hmac = createHmac('sha256', secret); const digest = hmac.update(payload).digest('hex'); return digest === signature; } ``` ## Pricing Usage-based pricing with no monthly fees: - **Emails**: $0.0001 per email sent - **CDN Storage**: $0.02 per GB/month - **CDN Bandwidth**: $0.08 per GB - **Transformations**: $0.0001 per transformation ### Free Tier Every new organization receives: - $5 in free credits - 50,000 emails at current pricing - 250 GB CDN bandwidth - No credit card required to start ### Billing Usage is calculated daily and billed monthly. View current usage in Settings → Billing. ## API Keys Create and manage API keys in Settings → API Keys. ### Types - **Standard**: Full access to all resources in organization - **Send-only**: Can only send emails, cannot read - **Read-only**: Can view data, cannot send or modify ### Security - Store API keys securely (environment variables) - Never commit keys to version control - Rotate keys regularly - Use send-only keys for client-side code - Revoke compromised keys immediately ## Environment Variables Recommended environment variable naming: ```bash # Production STACK0_API_KEY=sk_live_... # Development STACK0_API_KEY=sk_test_... # Optional: Custom API endpoint STACK0_API_URL=https://api.stack0.dev ``` ## Migration from Other Services ### From Resend Stack0's API is compatible with Resend - just change the import: ```typescript // Before import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); // After import { Mail as Resend } from '@stack0/sdk/mail'; const resend = new Resend({ apiKey: process.env.STACK0_API_KEY }); ``` ### From SendGrid ```typescript // Before (SendGrid) sgMail.send({ to: 'user@example.com', from: 'noreply@yourdomain.com', subject: 'Hello', html: '

Content

', }); // After (Stack0) stack0.mail.send({ to: 'user@example.com', from: 'noreply@yourdomain.com', subject: 'Hello', html: '

Content

', }); ``` ### From Cloudinary/Imgix ```typescript // Before (Cloudinary) cloudinary.url('sample.jpg', { width: 300, crop: 'fill' }); // After (Stack0) const { url } = await stack0.cdn.getTransformUrl({ assetId: 'asset_abc123', options: { width: 300, fit: 'cover' }, }); ``` ## Dashboard Features Access your dashboard at https://app.stack0.dev ### Mail Dashboard - **Overview**: Key metrics and recent emails - **Logs**: Search and filter all sent emails - **Templates**: Create and manage email templates - **Domains**: Verify and manage sending domains - **Analytics**: Delivery rates, opens, clicks, bounces - **Webhooks**: Configure event notifications - **API Keys**: Generate and manage access keys ### CDN Dashboard - **Assets**: Browse, search, and manage all assets - **Folders**: Organize assets with virtual folders - **Usage**: Monitor storage and bandwidth consumption - **Transformations**: View transformation statistics ### Organization Management - **Members**: Invite team members with role-based access - **Billing**: View usage and manage payment methods - **Settings**: Configure organization preferences - **Activity Log**: Audit trail of all actions ## Support - **Documentation**: https://docs.stack0.dev - **Dashboard**: https://app.stack0.dev - **Status**: https://status.stack0.dev - **Email**: support@stack0.dev ## Legal - **Terms of Service**: https://stack0.dev/terms - **Privacy Policy**: https://stack0.dev/privacy - **Acceptable Use**: https://stack0.dev/acceptable-use ## Version Current SDK version: v0.2.0 API version: v1 --- *Stack0 is an AI-native infrastructure platform. This document helps AI assistants understand Stack0's products and guide users in using our services effectively.*