@ovixa/email-client
v0.2.0
Published
Client SDK for Ovixa Email service
Maintainers
Readme
@ovixa/email-client
Client SDK for the Ovixa Email service. Send transactional emails using templates.
Installation
npm install @ovixa/email-client
# or
pnpm add @ovixa/email-clientQuick Start
import { OvixaEmail } from '@ovixa/email-client';
const email = new OvixaEmail({
apiKey: process.env.OVIXA_EMAIL_API_KEY,
defaultFrom: '[email protected]',
});
// Send a templated email
await email.send({
to: '[email protected]',
template: 'welcome',
data: { name: 'John' },
});
// Get available templates for this API key
const { templates } = await email.getTemplates();API Reference
Constructor
new OvixaEmail(config: EmailClientConfig)| Option | Type | Required | Default | Description |
| ---------------- | -------- | -------- | ------------------------ | ------------------------ |
| apiKey | string | Yes | - | Your API key |
| emailUrl | string | No | https://email.ovixa.io | Service URL |
| defaultFrom | string | No | - | Default sender address |
| defaultReplyTo | string | No | - | Default reply-to address |
| timeout | number | No | 30000 | Request timeout (ms) |
Methods
send(options) - Send Templated Email
const result = await email.send({
to: '[email protected]', // string or string[]
template: 'verification', // template ID
data: {
// template data
name: 'John',
verifyUrl: 'https://...',
},
from: '[email protected]', // optional, overrides default
replyTo: '[email protected]', // optional, overrides default
});
// Returns: { success: true, messageId: 'msg-xxx' }getTemplates() - List Available Templates
Returns templates available for your API key. If your API key has template restrictions configured, only the allowed templates will be returned.
const { templates } = await email.getTemplates();
for (const template of templates) {
console.log(`${template.id}: requires ${template.requiredData.join(', ')}`);
}rateLimitInfo - Get Rate Limit Status
// After any API call, check rate limit status
console.log(email.rateLimitInfo);
// { limit: 100, remaining: 95, reset: 1700000000 }Available Templates
| Template | Required Data | Description |
| ------------------ | ------------------------------------ | ----------------------------- |
| verification | name, verifyUrl | Email verification link |
| password-reset | name, resetUrl | Password reset link |
| welcome | name | Welcome message |
| login-alert | name, device, location, time | New login notification |
| account-deletion | name, deletedAt | Account deletion confirmation |
All templates also accept optional appName (defaults to "Ovixa").
Template Restrictions
API keys can be configured with template restrictions to limit which templates they can use:
- No restrictions: API key can use all templates (default)
- Restricted: API key can only use specific templates (e.g.,
welcome,verification) - Disabled: API key cannot use any templates (empty string)
Client vs Server Validation
The SDK validates that a template name is valid (i.e., it exists as a known template type), but it does not check whether your API key is authorized to use that template. Server-side validation is authoritative — a request may pass client-side validation but still be rejected by the server with a TEMPLATE_NOT_ALLOWED error (HTTP 403) if your API key doesn't have access.
| Validation Layer | Checks | Error Code |
| ---------------- | ---------------------------------- | ---------------------- |
| Client (SDK) | Template name is a known type | INVALID_TEMPLATE |
| Server | API key is authorized for template | TEMPLATE_NOT_ALLOWED |
Best Practices for Restricted Keys
If your API key may have template restrictions, call getTemplates() first to discover which templates are available:
const email = new OvixaEmail({
apiKey: process.env.OVIXA_EMAIL_API_KEY,
defaultFrom: '[email protected]',
});
// Discover available templates for this API key
const { templates } = await email.getTemplates();
const availableIds = templates.map((t) => t.id);
// Check before sending
if (availableIds.includes('welcome')) {
await email.send({
to: '[email protected]',
template: 'welcome',
data: { name: 'John' },
});
}Handling Restricted Template Errors
When calling send() with a template your API key cannot access, the server returns a TEMPLATE_NOT_ALLOWED error:
try {
await email.send({
to: '[email protected]',
template: 'login-alert', // Valid template, but key may not have access
data: { name: 'John', device: 'Chrome', location: 'NY', time: 'now' },
});
} catch (error) {
if (error instanceof OvixaEmailError && error.code === 'TEMPLATE_NOT_ALLOWED') {
console.log('This API key cannot send login-alert emails');
}
}Webhook Verification
Verify webhook signatures from email delivery events:
import { verifyWebhook, extractWebhookHeaders } from '@ovixa/email-client/webhooks';
// In your webhook handler
async function handleWebhook(request: Request) {
const payload = await request.text();
const headers = extractWebhookHeaders(request.headers);
try {
const event = await verifyWebhook({
payload,
signature: headers.signature,
timestamp: headers.timestamp,
messageId: headers.messageId,
secret: process.env.WEBHOOK_SECRET,
});
switch (event.type) {
case 'email.delivered':
console.log('Email delivered:', event.data.email_id);
break;
case 'email.bounced':
console.log('Email bounced:', event.data.email_id);
break;
case 'email.complained':
console.log('Spam complaint:', event.data.email_id);
break;
}
return new Response('OK', { status: 200 });
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response('Invalid signature', { status: 401 });
}
throw error;
}
}Webhook Event Types
| Event | Description |
| ------------------ | ---------------------------------- |
| email.sent | Email accepted by provider |
| email.delivered | Email delivered to recipient |
| email.bounced | Email bounced (hard/soft) |
| email.complained | Recipient marked as spam |
| email.opened | Email opened (if tracking enabled) |
| email.clicked | Link clicked (if tracking enabled) |
Error Handling
import { OvixaEmail, OvixaEmailError } from '@ovixa/email-client';
try {
await email.send({ ... });
} catch (error) {
if (error instanceof OvixaEmailError) {
console.error(`Error [${error.code}]: ${error.message}`);
if (error.code === 'RATE_LIMITED' && error.retryAfter) {
console.log(`Retry after ${error.retryAfter} seconds`);
}
}
}Error Codes
| Code | Description |
| ---------------------- | ---------------------------------------------- |
| MISSING_API_KEY | API key not provided |
| INVALID_API_KEY | API key is invalid |
| INVALID_TEMPLATE | Template ID not found |
| TEMPLATE_NOT_ALLOWED | API key is not authorized to use this template |
| MISSING_DATA | Required template data missing |
| INVALID_EMAIL | Invalid email address format |
| RATE_LIMITED | Too many requests (check retryAfter) |
| PROVIDER_ERROR | Email provider error |
| NETWORK_ERROR | Network connectivity error |
| TIMEOUT | Request timed out |
| REQUEST_FAILED | Generic request failure |
Security Notes
- API keys are never logged or included in error messages
- HTTPS is enforced - HTTP URLs trigger a warning (except localhost)
- Email format validation is performed client-side before sending
- Webhook signatures use HMAC-SHA256 with constant-time comparison
- Timestamp validation prevents replay attacks (5-minute window)
- Rate limit info is exposed for client-side handling
TypeScript Support
Full TypeScript support with exported types:
import type {
EmailClientConfig,
EmailTemplate,
SendEmailOptions,
SendEmailResponse,
TemplateInfo,
TemplatesResponse,
RateLimitInfo,
EmailErrorCode,
} from '@ovixa/email-client';
import type {
WebhookVerifyOptions,
WebhookEvent,
WebhookEventType,
WebhookEventData,
} from '@ovixa/email-client/webhooks';License
MIT
