> ## Documentation Index
> Fetch the complete documentation index at: https://docs.senderr.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending an email via Senderr using JavaScript

> Learn how to integrate Senderr email templates into your JavaScript and Node.js applications

# Sending an email via Senderr using JavaScript

This guide shows you how to integrate Senderr email templates into your JavaScript and Node.js applications, with examples for both server-side and client-side integrations.

## Prerequisites

Before you begin, make sure you have:

* Node.js 16 or higher
* A Senderr account with templates in your library
* A Senderr API key ([generate one here](https://senderr.dev/dashboard/settings))
* A Resend account for sending emails ([sign up here](https://resend.com))

## Installation

Install the required packages:

```bash theme={null}
npm install axios resend
# or
yarn add axios resend
```

For TypeScript projects:

```bash theme={null}
npm install -D @types/node typescript
# or
yarn add -D @types/node typescript
```

## TypeScript Setup

### Type Definitions

Create type definitions for better development experience:

```typescript theme={null}
// types/senderr.ts
export interface Template {
  id: string;
  name: string;
  description: string;
  category: string;
  price: number;
  is_public: boolean;
  created_at: string;
  updated_at: string;
}

export interface TemplateSchema {
  required: string[];
  optional: string[];
  properties: {
    [key: string]: {
      type: string;
      description?: string;
      default?: any;
    };
  };
}

export interface RenderRequest {
  variables: Record<string, any>;
  format?: 'html' | 'react';
}

export interface RenderResponse {
  html: string;
  subject?: string;
  success: boolean;
}

export interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
  message?: string;
}
```

### Senderr Client

Create a TypeScript client for the Senderr API:

```typescript theme={null}
// lib/senderr-client.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
import { Template, TemplateSchema, RenderRequest, RenderResponse, ApiResponse } from '../types/senderr';

export class SenderrClient {
  private client: AxiosInstance;

  constructor(apiKey?: string) {
    const key = apiKey || process.env.SENDERR_API_KEY;

    if (!key) {
      throw new Error('Senderr API key is required');
    }

    this.client = axios.create({
      baseURL: 'https://senderr.dev/api/v1',
      headers: {
        'Authorization': `Bearer ${key}`,
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });

    // Response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response,
      (error: AxiosError) => {
        if (error.response?.data) {
          throw new Error(error.response.data as string);
        }
        throw error;
      }
    );
  }

  async getLibrary(): Promise<Template[]> {
    try {
      const response = await this.client.get<ApiResponse<{ templates: Template[] }>>('/library');
      return response.data.data?.templates || [];
    } catch (error) {
      throw new Error(`Failed to fetch library: ${error}`);
    }
  }

  async getTemplateSchema(templateId: string): Promise<TemplateSchema> {
    try {
      const response = await this.client.get<ApiResponse<TemplateSchema>>(`/templates/${templateId}/schema`);
      if (!response.data.data) {
        throw new Error('Template schema not found');
      }
      return response.data.data;
    } catch (error) {
      throw new Error(`Failed to fetch template schema: ${error}`);
    }
  }

  async renderTemplate(templateId: string, request: RenderRequest): Promise<RenderResponse> {
    try {
      const response = await this.client.post<ApiResponse<RenderResponse>>(
        `/templates/${templateId}/render`,
        request
      );

      if (!response.data.data) {
        throw new Error('Template rendering failed');
      }

      return response.data.data;
    } catch (error) {
      throw new Error(`Failed to render template: ${error}`);
    }
  }

  async findTemplateByName(name: string): Promise<Template | null> {
    const templates = await this.getLibrary();
    return templates.find(t => t.name.toLowerCase().includes(name.toLowerCase())) || null;
  }
}
```

### Email Service

Create an email service using Resend:

```typescript theme={null}
// lib/email-service.ts
import { Resend } from 'resend';

export interface SendEmailOptions {
  to: string | string[];
  subject: string;
  html: string;
  from?: string;
  replyTo?: string;
  cc?: string[];
  bcc?: string[];
}

export interface SendEmailResult {
  success: boolean;
  id?: string;
  error?: string;
}

export class EmailService {
  private resend: Resend;
  private defaultFrom: string;

  constructor(apiKey?: string, defaultFrom = 'noreply@yourcompany.com') {
    const key = apiKey || process.env.RESEND_API_KEY;

    if (!key) {
      throw new Error('Resend API key is required');
    }

    this.resend = new Resend(key);
    this.defaultFrom = defaultFrom;
  }

  async sendEmail(options: SendEmailOptions): Promise<SendEmailResult> {
    try {
      const result = await this.resend.emails.send({
        from: options.from || this.defaultFrom,
        to: Array.isArray(options.to) ? options.to : [options.to],
        subject: options.subject,
        html: options.html,
        reply_to: options.replyTo,
        cc: options.cc,
        bcc: options.bcc,
      });

      return {
        success: true,
        id: result.data?.id,
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error',
      };
    }
  }

  async sendBulkEmails(emails: SendEmailOptions[]): Promise<SendEmailResult[]> {
    // Send emails concurrently with rate limiting
    const results: SendEmailResult[] = [];
    const batchSize = 10; // Adjust based on Resend limits

    for (let i = 0; i < emails.length; i += batchSize) {
      const batch = emails.slice(i, i + batchSize);
      const batchPromises = batch.map(email => this.sendEmail(email));
      const batchResults = await Promise.allSettled(batchPromises);

      batchResults.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          results.push(result.value);
        } else {
          results.push({
            success: false,
            error: result.reason?.message || 'Failed to send email',
          });
        }
      });

      // Add delay between batches to respect rate limits
      if (i + batchSize < emails.length) {
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }

    return results;
  }
}
```

## Complete Example: Welcome Email Service

```typescript theme={null}
// services/welcome-email.ts
import { SenderrClient } from '../lib/senderr-client';
import { EmailService, SendEmailOptions } from '../lib/email-service';

export interface User {
  email: string;
  firstName: string;
  lastName: string;
  company?: string;
  signupDate?: Date;
}

export class WelcomeEmailService {
  private senderr: SenderrClient;
  private emailService: EmailService;
  private welcomeTemplateId: string;

  constructor(welcomeTemplateId: string) {
    this.senderr = new SenderrClient();
    this.emailService = new EmailService();
    this.welcomeTemplateId = welcomeTemplateId;
  }

  async sendWelcomeEmail(user: User): Promise<{ success: boolean; error?: string }> {
    try {
      // Validate template exists and get schema
      const schema = await this.senderr.getTemplateSchema(this.welcomeTemplateId);

      // Prepare variables
      const variables = {
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        companyName: user.company || 'there',
        signupDate: user.signupDate?.toLocaleDateString() || new Date().toLocaleDateString(),
        dashboardUrl: 'https://yourapp.com/dashboard',
        supportEmail: 'support@yourcompany.com',
      };

      // Validate required variables
      this.validateVariables(schema, variables);

      // Render template
      const rendered = await this.senderr.renderTemplate(this.welcomeTemplateId, {
        variables,
        format: 'html',
      });

      // Send email
      const emailResult = await this.emailService.sendEmail({
        to: user.email,
        subject: `Welcome to our platform, ${user.firstName}!`,
        html: rendered.html,
      });

      return emailResult;

    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error',
      };
    }
  }

  private validateVariables(schema: any, variables: Record<string, any>): void {
    const missing = schema.required?.filter((key: string) => !(key in variables)) || [];

    if (missing.length > 0) {
      throw new Error(`Missing required variables: ${missing.join(', ')}`);
    }
  }

  async sendBulkWelcomeEmails(users: User[]): Promise<Array<{ user: User; result: { success: boolean; error?: string } }>> {
    const results = await Promise.allSettled(
      users.map(async (user) => ({
        user,
        result: await this.sendWelcomeEmail(user),
      }))
    );

    return results.map((result, index) => {
      if (result.status === 'fulfilled') {
        return result.value;
      } else {
        return {
          user: users[index],
          result: {
            success: false,
            error: result.reason?.message || 'Failed to send welcome email',
          },
        };
      }
    });
  }
}

// Usage
async function main() {
  const welcomeService = new WelcomeEmailService('your_welcome_template_id');

  const newUser: User = {
    email: 'john.doe@example.com',
    firstName: 'John',
    lastName: 'Doe',
    company: 'Acme Corp',
    signupDate: new Date(),
  };

  const result = await welcomeService.sendWelcomeEmail(newUser);

  if (result.success) {
    console.log('Welcome email sent successfully!');
  } else {
    console.error('Failed to send welcome email:', result.error);
  }
}
```

## Express.js Integration

```typescript theme={null}
// routes/email.ts
import express from 'express';
import { body, validationResult } from 'express-validator';
import { SenderrClient } from '../lib/senderr-client';
import { EmailService } from '../lib/email-service';

const router = express.Router();
const senderr = new SenderrClient();
const emailService = new EmailService();

// Send email endpoint
router.post('/send',
  [
    body('templateId').notEmpty().withMessage('Template ID is required'),
    body('to').isEmail().withMessage('Valid email address is required'),
    body('variables').isObject().withMessage('Variables must be an object'),
    body('subject').optional().isString(),
  ],
  async (req, res) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(400).json({ success: false, errors: errors.array() });
      }

      const { templateId, to, variables, subject } = req.body;

      // Render template
      const rendered = await senderr.renderTemplate(templateId, {
        variables,
        format: 'html',
      });

      // Send email
      const result = await emailService.sendEmail({
        to,
        subject: subject || 'Email from Senderr',
        html: rendered.html,
      });

      res.json(result);

    } catch (error) {
      console.error('Email sending error:', error);
      res.status(500).json({
        success: false,
        error: error instanceof Error ? error.message : 'Internal server error',
      });
    }
  }
);

// Get templates endpoint
router.get('/templates', async (req, res) => {
  try {
    const templates = await senderr.getLibrary();
    res.json({ success: true, templates });
  } catch (error) {
    console.error('Template fetch error:', error);
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Internal server error',
    });
  }
});

// Get template schema endpoint
router.get('/templates/:id/schema', async (req, res) => {
  try {
    const { id } = req.params;
    const schema = await senderr.getTemplateSchema(id);
    res.json({ success: true, schema });
  } catch (error) {
    console.error('Schema fetch error:', error);
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Internal server error',
    });
  }
});

export default router;

// app.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import emailRoutes from './routes/email';

const app = express();

// Security middleware
app.use(helmet());
app.use(cors());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
});
app.use(limiter);

// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/email', emailRoutes);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

## Next.js Integration

### API Route

```typescript theme={null}
// pages/api/send-email.ts (Pages Router)
// or app/api/send-email/route.ts (App Router)

import { NextRequest, NextResponse } from 'next/server';
import { SenderrClient } from '@/lib/senderr-client';
import { EmailService } from '@/lib/email-service';

const senderr = new SenderrClient();
const emailService = new EmailService();

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { templateId, to, variables, subject } = body;

    // Validate input
    if (!templateId || !to || !variables) {
      return NextResponse.json(
        { success: false, error: 'Missing required fields' },
        { status: 400 }
      );
    }

    // Render template
    const rendered = await senderr.renderTemplate(templateId, {
      variables,
      format: 'html',
    });

    // Send email
    const result = await emailService.sendEmail({
      to,
      subject: subject || 'Email from Senderr',
      html: rendered.html,
    });

    return NextResponse.json(result);

  } catch (error) {
    console.error('Email API error:', error);
    return NextResponse.json(
      {
        success: false,
        error: error instanceof Error ? error.message : 'Internal server error'
      },
      { status: 500 }
    );
  }
}
```

### React Hook

```typescript theme={null}
// hooks/use-senderr.ts
import { useState, useEffect } from 'react';
import { Template } from '@/types/senderr';

export function useSenderr() {
  const [templates, setTemplates] = useState<Template[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const fetchTemplates = async () => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/templates');
      const data = await response.json();

      if (data.success) {
        setTemplates(data.templates);
      } else {
        setError(data.error || 'Failed to fetch templates');
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setLoading(false);
    }
  };

  const sendEmail = async (templateId: string, to: string, variables: Record<string, any>, subject?: string) => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/send-email', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ templateId, to, variables, subject }),
      });

      const data = await response.json();

      if (!data.success) {
        setError(data.error || 'Failed to send email');
        return false;
      }

      return true;
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      return false;
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchTemplates();
  }, []);

  return {
    templates,
    loading,
    error,
    sendEmail,
    refetchTemplates: fetchTemplates,
  };
}
```

## JavaScript (Plain) Examples

For plain JavaScript projects without TypeScript:

```javascript theme={null}
// lib/senderr-client.js
const axios = require('axios');

class SenderrClient {
  constructor(apiKey) {
    this.apiKey = apiKey || process.env.SENDERR_API_KEY;

    if (!this.apiKey) {
      throw new Error('Senderr API key is required');
    }

    this.client = axios.create({
      baseURL: 'https://senderr.dev/api/v1',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });
  }

  async getLibrary() {
    try {
      const response = await this.client.get('/library');
      return response.data.templates || [];
    } catch (error) {
      throw new Error(`Failed to fetch library: ${error.message}`);
    }
  }

  async renderTemplate(templateId, variables, format = 'html') {
    try {
      const response = await this.client.post(`/templates/${templateId}/render`, {
        variables,
        format,
      });

      return response.data;
    } catch (error) {
      throw new Error(`Failed to render template: ${error.message}`);
    }
  }
}

module.exports = { SenderrClient };

// Usage
const { SenderrClient } = require('./lib/senderr-client');
const { Resend } = require('resend');

async function sendWelcomeEmail(userEmail, firstName) {
  const senderr = new SenderrClient();
  const resend = new Resend(process.env.RESEND_API_KEY);

  try {
    // Render template
    const rendered = await senderr.renderTemplate('welcome_template_id', {
      firstName,
      email: userEmail,
      dashboardUrl: 'https://yourapp.com/dashboard',
    });

    // Send email
    const result = await resend.emails.send({
      from: 'noreply@yourcompany.com',
      to: userEmail,
      subject: `Welcome, ${firstName}!`,
      html: rendered.html,
    });

    console.log('Email sent:', result.data.id);
    return true;

  } catch (error) {
    console.error('Failed to send email:', error.message);
    return false;
  }
}

// Call the function
sendWelcomeEmail('user@example.com', 'John');
```

## Testing

```typescript theme={null}
// tests/senderr-client.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios from 'axios';
import { SenderrClient } from '../lib/senderr-client';

vi.mock('axios');
const mockedAxios = vi.mocked(axios);

describe('SenderrClient', () => {
  let client: SenderrClient;

  beforeEach(() => {
    vi.clearAllMocks();
    mockedAxios.create.mockReturnValue(mockedAxios as any);
    client = new SenderrClient('test-api-key');
  });

  it('should fetch templates successfully', async () => {
    const mockTemplates = [{ id: '1', name: 'Test Template' }];
    mockedAxios.get.mockResolvedValue({
      data: { data: { templates: mockTemplates } }
    });

    const templates = await client.getLibrary();

    expect(templates).toEqual(mockTemplates);
    expect(mockedAxios.get).toHaveBeenCalledWith('/library');
  });

  it('should render template successfully', async () => {
    const mockRendered = { html: '<h1>Hello John</h1>' };
    mockedAxios.post.mockResolvedValue({
      data: { data: mockRendered }
    });

    const result = await client.renderTemplate('template-id', { name: 'John' });

    expect(result).toEqual(mockRendered);
    expect(mockedAxios.post).toHaveBeenCalledWith('/templates/template-id/render', {
      variables: { name: 'John' },
      format: 'html'
    });
  });
});
```

## Best Practices

### 1. Error Handling

Always implement comprehensive error handling:

```typescript theme={null}
class ApiError extends Error {
  constructor(message: string, public statusCode?: number) {
    super(message);
    this.name = 'ApiError';
  }
}

// In your client methods
try {
  const response = await this.client.get('/endpoint');
  return response.data;
} catch (error) {
  if (axios.isAxiosError(error)) {
    throw new ApiError(
      error.response?.data?.message || error.message,
      error.response?.status
    );
  }
  throw error;
}
```

### 2. Rate Limiting & Retries

Implement retry logic with exponential backoff:

```typescript theme={null}
async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = baseDelay * Math.pow(2, attempt - 1);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw new Error('Max retries exceeded');
}
```

### 3. Caching

Implement caching for frequently accessed data:

```typescript theme={null}
class CachedSenderrClient extends SenderrClient {
  private cache = new Map<string, { data: any; expiry: number }>();
  private cacheTimeout = 5 * 60 * 1000; // 5 minutes

  async getTemplateSchema(templateId: string) {
    const cacheKey = `schema:${templateId}`;
    const cached = this.cache.get(cacheKey);

    if (cached && cached.expiry > Date.now()) {
      return cached.data;
    }

    const schema = await super.getTemplateSchema(templateId);
    this.cache.set(cacheKey, {
      data: schema,
      expiry: Date.now() + this.cacheTimeout
    });

    return schema;
  }
}
```

### 4. Environment Configuration

Use proper environment configuration:

```typescript theme={null}
// config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  SENDERR_API_KEY: z.string(),
  RESEND_API_KEY: z.string(),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

export const env = envSchema.parse(process.env);
```

## Next Steps

* [Check out n8n integration](/guides/integrations/n8n)
* [Learn about Python integration](/guides/integrations/python)
* [Explore the API reference](/api-reference/introduction)
* [Browse available templates](https://senderr.dev/templates)

Need help? Contact [support@senderr.dev](mailto:support@senderr.dev)
