Mirra
Build

Developing Scripts

This guide provides comprehensive documentation for creating and managing scripts in Mirra. It covers script structure, runtime environments, configuration options, version control, resource access, and best practices for production deployments.


Script Structure

Scripts consist of a handler function that receives event data and returns a result. The handler signature is consistent across both supported runtimes.

Node.js Handler

export async function handler(event, context) {
  // Access event data
  const inputData = event.data;
  
  // Process the data
  console.log('Processing event:', event);
  
  // Return result
  return {
    success: true,
    data: processedData
  };
}

Parameters:

event

Input data object containing the trigger data. For manual executions, contains the data passed to the execute endpoint. For event-driven executions, contains normalized event fields and the full event object.

context

Execution context object with metadata about the script execution, including scriptId, apiKey, and executionId.

Return Value:

The handler returns any JSON-serializable object. By convention, responses include a success boolean and a data object, but this structure is not enforced.

Python Handler

async def handler(event, context):
    # Access event data
    input_data = event.get('data')
    
    # Process the data
    print(f"Processing event: {event}")
    
    # Return result
    return {
        "success": True,
        "data": processed_data
    }

The Python handler follows the same parameter and return value conventions as Node.js.


Event Data Structure

Event-driven scripts receive normalized event data that works consistently across all trigger types. This enables scripts to handle multiple event sources without modification.

Normalized Fields

All events provide these standard fields:

export async function handler(event) {
  // These fields work with ANY trigger type
  const text = event.text;           // Message/email body/content
  const sender = event.sender;       // Who triggered it
  const timestamp = event.timestamp; // When it happened
  const metadata = event.metadata;   // Type-specific data
  
  // Static input from assignments
  const apiKey = event.apiKey;
  const config = event.config;
  
  return { success: true };
}

Full Event Access

Power users can access the complete original event object:

export async function handler(event) {
  // Access the full original event
  const fullEvent = event.event;
  
  // Telegram-specific fields (NEW structure)
  if (fullEvent.type === 'telegram.message') {
    const chatId = fullEvent.telegram.chatId;
    const isGroup = fullEvent.telegram.isGroupChat;
  }
  
  // Legacy field access still works
  if (fullEvent.type === 'telegram.message') {
    const chatIdLegacy = fullEvent.telegramChatId;    // Backward compatible
    const isGroupLegacy = fullEvent.telegramIsGroupChat;  // Backward compatible
  }
  
  // Email-specific fields (NEW structure)
  if (fullEvent.type === 'gmail.email_received') {
    const subject = fullEvent.gmail.subject;
    const from = fullEvent.gmail.from.email;
  }
  
  // Legacy field access still works
  if (fullEvent.type === 'gmail.email_received') {
    const subjectLegacy = fullEvent.emailSubject;  // Backward compatible
    const fromLegacy = fullEvent.emailFrom;        // Backward compatible
  }
  
  return { success: true };
}

Defensive Coding Pattern

Scripts can use defensive patterns to handle any trigger type:

export async function handler(event) {
  // Works with any trigger type
  const message = event.text 
    || event.message 
    || event.content 
    || "No message";
    
  console.log(`Received: ${message}`);
  return { success: true };
}

This approach enables zero-configuration scripts that work with any trigger without setup, making them naturally reusable and LLM-friendly.


Runtime Environments

Mirra supports two runtime environments with different capabilities and package ecosystems.

Node.js 18

The Node.js runtime provides modern JavaScript and TypeScript support with access to popular NPM packages.

Features:

  • TypeScript support with type checking
  • ES modules and CommonJS
  • Async/await and Promises
  • NPM package ecosystem
  • 30-second default timeout

Available Packages:

Standard Node.js built-ins and popular packages including:

  • lodash - Utility functions
  • axios - HTTP client
  • date-fns - Date manipulation
  • uuid - UUID generation
  • crypto - Cryptographic functions

Example with External Package:

import { format, addDays } from 'date-fns';
 
export async function handler(event, context) {
  const now = new Date();
  const tomorrow = addDays(now, 1);
  
  return {
    success: true,
    today: format(now, 'yyyy-MM-dd'),
    tomorrow: format(tomorrow, 'yyyy-MM-dd')
  };
}

Python 3.11

The Python runtime provides modern Python syntax with async/await support and access to popular pip packages.

Features:

  • Modern Python 3.11 syntax
  • Type hints and annotations
  • Async/await support
  • Pip package ecosystem
  • 30-second default timeout

Available Packages:

Standard Python library and popular packages including:

  • requests - HTTP client
  • pandas - Data analysis
  • numpy - Numerical computing
  • datetime - Date and time utilities
  • json - JSON encoding/decoding

Example with External Package:

from datetime import datetime, timedelta
 
async def handler(event, context):
    now = datetime.now()
    tomorrow = now + timedelta(days=1)
    
    return {
        "success": True,
        "today": now.isoformat(),
        "tomorrow": tomorrow.isoformat()
    }

Configuration

Scripts accept configuration parameters that control execution behavior, resource access, and cost limits.

Configuration Object

{
  "config": {
    "runtime": "nodejs18",
    "timeout": 30,
    "memory": 256,
    "maxCostPerExecution": 1.0,
    "maxExecutionsPerDay": 100,
    "allowedResources": []
  }
}

Configuration Parameters

runtime

Execution environment. Must be "nodejs18" or "python3.11". Default: "nodejs18".

Choose based on your preferred language and required packages. Both runtimes provide equivalent functionality through the Mirra SDK.

timeout

Maximum execution time in seconds. Range: 1-300. Default: 30.

Scripts exceeding this limit are terminated with a timeout error. Increase for long-running operations like large data processing or external API calls with slow response times.

memory

Memory allocation in MB. Range: 128-3008. Default: 128.

Higher memory allocation provides proportionally more CPU. Increase for memory-intensive operations like large dataset processing or complex computations.

maxCostPerExecution

Maximum cost per execution in USD. Default: 1.0.

Executions exceeding this limit fail with a cost limit error. This prevents runaway costs from expensive resource calls or long execution times.

maxExecutionsPerDay

Maximum executions per 24-hour period. Default: 100.

Scripts stop executing when this limit is reached until the next day. This prevents excessive costs from high-frequency event triggers.

allowedResources

Array of resource IDs the script can access. Empty array (default) denies all resource access.

Scripts can only call resources explicitly whitelisted in this array. See Resources for details on creating and using resources.

Updating Configuration

Update script configuration using the PATCH endpoint:

curl -X PATCH https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "config": {
      "timeout": 60,
      "memory": 512,
      "maxExecutionsPerDay": 200
    }
  }'

Configuration changes take effect immediately for new executions. In-progress executions use the configuration active at execution start.


Version Control

Scripts support version control for safe deployments and rollbacks. Each version is immutable once created.

Creating Versions

Create a new version with updated code:

curl -X POST https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/versions \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "export async function handler(event) { return { v2: true }; }",
    "commitMessage": "Added new feature"
  }'

Response:

{
  "success": true,
  "data": {
    "versionNumber": 2,
    "commitMessage": "Added new feature",
    "createdAt": "2025-11-12T10:00:00Z"
  }
}

Listing Versions

Retrieve all versions for a script:

curl https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/versions \
  -H "X-API-Key: YOUR_API_KEY"

Deploying Specific Versions

Deploy a specific version by including the version number:

curl -X POST https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/deploy \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"version": 2}'

Omitting the version number deploys the latest version.


Accessing Resources

Scripts access external APIs through Resources. Resources encapsulate API credentials and provide a secure interface for making external calls.

Granting Resource Access

Add resource IDs to the script's allowedResources configuration:

curl -X PATCH https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "config": {
      "allowedResources": ["resource_abc123", "resource_def456"]
    }
  }'

Calling Resources

Use the Mirra SDK to call resources from within scripts:

export async function handler(event, context) {
  // Call a resource method
  const weather = await mirra.resources.call(
    'resource_abc123',
    'getWeather',
    { city: 'San Francisco' }
  );
  
  return {
    success: true,
    weather
  };
}

Scripts can only access resources explicitly listed in allowedResources. Attempts to call non-whitelisted resources fail with a permission error.


Using the Mirra SDK

Scripts have access to the mirra global object, which provides methods for interacting with Mirra services.

Memory Operations

Store and retrieve data in Mirra memory:

// Create memory entry
await mirra.memory.create({
  type: 'note',
  content: 'Important information',
  metadata: { category: 'work', priority: 'high' }
});
 
// Search memory
const results = await mirra.memory.search({
  query: 'important',
  type: 'note',
  limit: 10
});
 
// Update memory entry
await mirra.memory.update('memory_id', {
  content: 'Updated information'
});
 
// Delete memory entry
await mirra.memory.delete('memory_id');

Telegram Operations

Send messages and interact with Telegram:

// Send message
await mirra.telegram.sendMessage({
  chatId: event.telegramChatId,
  text: 'Hello from script!',
  parseMode: 'Markdown'
});
 
// Send photo
await mirra.telegram.sendPhoto({
  chatId: event.telegramChatId,
  photo: 'https://example.com/image.jpg',
  caption: 'Photo caption'
});

Calendar Operations

Create and manage calendar events:

// Create event
await mirra.calendar.createEvent({
  summary: 'Team Meeting',
  description: 'Weekly sync',
  startTime: '2025-11-15T10:00:00Z',
  endTime: '2025-11-15T11:00:00Z',
  location: 'Conference Room A'
});
 
// Update event
await mirra.calendar.updateEvent('event_id', {
  summary: 'Updated Meeting Title'
});
 
// Delete event
await mirra.calendar.deleteEvent('event_id');

Gmail Operations

Send emails and manage Gmail:

// Send email
await mirra.gmail.sendEmail({
  to: 'user@example.com',
  subject: 'Hello from Mirra',
  body: 'Email body content',
  html: '<p>HTML email content</p>'
});
 
// Add label
await mirra.gmail.addLabel({
  emailId: 'email_id',
  label: 'Important'
});

Best Practices

Error Handling

Always wrap script logic in try-catch blocks to handle errors gracefully:

export async function handler(event, context) {
  try {
    const result = await riskyOperation(event.data);
    return { success: true, result };
  } catch (error) {
    console.error('Operation failed:', error);
    return {
      success: false,
      error: error.message,
      stack: error.stack
    };
  }
}

This pattern ensures scripts always return a response, even when errors occur, making debugging easier.

Logging

Use console methods for debugging and monitoring:

console.log('Info message');        // General information
console.info('Data:', data);        // Detailed information
console.warn('Warning!');           // Warnings
console.error('Error!', error);     // Errors
console.debug('Debug info:', obj);  // Debug information

All console output is captured in execution logs and available through the SDK API.

Performance Optimization

Initialize expensive resources outside the handler function to reuse them across invocations:

// ✅ Good - Initialized once, reused across invocations
const client = new APIClient();
 
export async function handler(event, context) {
  return await client.process(event.data);
}
// ❌ Bad - Created on every invocation
export async function handler(event, context) {
  const client = new APIClient(); // Slow!
  return await client.process(event.data);
}

The Mirra platform reuses execution containers across invocations, so global variables persist between executions.

Timeout Management

For long-running operations, implement timeout handling to prevent execution termination:

export async function handler(event, context) {
  // Set timeout to 90% of configured limit
  const timeoutMs = 27000; // 27 seconds for 30s limit
  
  const promise = longOperation(event.data);
  const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), timeoutMs)
  );
  
  try {
    const result = await Promise.race([promise, timeout]);
    return { success: true, result };
  } catch (error) {
    return { success: false, error: error.message };
  }
}

Memory Management

Process large datasets in batches to avoid memory limits:

export async function handler(event, context) {
  const items = event.data.items;
  const batchSize = 100;
  const results = [];
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await processBatch(batch);
    results.push(...batchResults);
    
    // Optional: Clear memory between batches
    if (global.gc) global.gc();
  }
  
  return { success: true, results };
}

Script Lifecycle

Scripts progress through several states during their lifecycle:

Draft

Initial state after creation. Scripts in draft state:

  • Have not been deployed
  • Cannot be executed
  • Can have code modified freely
  • Do not consume resources

Deployed

Active state after deployment. Scripts in deployed state:

  • Are deployed to the Mirra platform
  • Can be executed manually via API
  • Can subscribe to events
  • Consume resources during execution

Published

Optional state for marketplace scripts. Published scripts:

  • Appear in marketplace listings
  • Can be installed by other users
  • Support monetization (free, pay-per-execution, subscription)
  • Cannot have code modified (create new versions instead)

Archived

Final state for retired scripts. Archived scripts:

  • Cannot be executed
  • Preserve execution history
  • Can be restored to deployed state
  • Do not consume resources

Updating Scripts

Update Metadata

Modify script name, description, or other metadata:

curl -X PATCH https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Script Name",
    "description": "Updated description"
  }'

Update Code

Create a new version with updated code, then deploy:

# 1. Create new version
curl -X POST https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/versions \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "export async function handler(event) { return { v2: true }; }",
    "commitMessage": "Bug fix"
  }'
 
# 2. Deploy new version
curl -X POST https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/deploy \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"version": 2}'

Deleting Scripts

Delete a script and all associated data:

curl -X DELETE https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID \
  -H "X-API-Key: YOUR_API_KEY"

This operation:

  • Deletes the script and all versions
  • Deletes execution history
  • Removes all event subscriptions
  • Removes the deployed function from the platform

This action cannot be undone! Ensure you want to permanently delete the script and all associated data.


Advanced Patterns

Stateful Scripts

Use memory to maintain state across executions:

export async function handler(event, context) {
  // Load state
  const state = await mirra.memory.search({
    query: 'script-state',
    type: 'state',
    limit: 1
  });
  
  let counter = state[0]?.metadata?.counter || 0;
  counter++;
  
  // Save state
  await mirra.memory.create({
    type: 'state',
    content: 'script-state',
    metadata: { counter, lastUpdated: new Date().toISOString() }
  });
  
  return { success: true, counter };
}

Conditional Execution

Implement business logic to control when scripts execute:

export async function handler(event, context) {
  // Only process during business hours (9 AM - 5 PM)
  const hour = new Date().getHours();
  if (hour < 9 || hour > 17) {
    return { 
      success: false, 
      reason: 'Outside business hours',
      hour 
    };
  }
  
  // Process event
  return await processEvent(event);
}

Chaining Scripts

Execute multiple scripts in sequence:

export async function handler(event, context) {
  // Execute first script
  const result1 = await mirra.scripts.execute('script_123', {
    data: event.data
  });
  
  // Use result in second script
  const result2 = await mirra.scripts.execute('script_456', {
    data: result1.data
  });
  
  return { 
    success: true, 
    finalResult: result2.data 
  };
}

Monitoring and Debugging

View Metrics

Retrieve execution metrics for monitoring:

curl https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/metrics \
  -H "X-API-Key: YOUR_API_KEY"

Response:

{
  "totalExecutions": 1547,
  "totalCost": 1.23,
  "avgDuration": 234,
  "successRate": 0.98,
  "errorRate": 0.02,
  "lastExecutedAt": "2025-11-12T10:00:00Z"
}

View Execution Details

Retrieve detailed information for a specific execution:

curl https://api.getmirra.app/api/sdk/v1/scripts/executions/EXECUTION_ID \
  -H "X-API-Key: YOUR_API_KEY"

The response includes:

  • Complete execution logs
  • Error stack traces
  • Resource call details
  • Duration and cost breakdown
  • Trigger information

Troubleshooting

Timeout Errors

Symptoms: Script fails with timeout error

Solutions:

  1. Increase timeout in configuration (up to 300 seconds)
  2. Optimize slow operations (database queries, API calls)
  3. Use async operations properly to avoid blocking
  4. Cache expensive computations outside the handler

Example Fix:

// ❌ Synchronous blocking
function processSync(data) {
  let result = 0;
  for (let i = 0; i < 1000000; i++) {
    result += data[i];
  }
  return result;
}
 
// ✅ Async non-blocking
async function processAsync(data) {
  return await Promise.all(
    data.map(async item => await processItem(item))
  );
}

Memory Errors

Symptoms: Out of memory errors during execution

Solutions:

  1. Increase memory allocation in configuration
  2. Process data in smaller batches
  3. Avoid loading large files entirely into memory
  4. Use streaming for large datasets

Example Fix:

// ❌ Load all data at once
const allData = await loadAllData(); // 500MB
const processed = allData.map(process);
 
// ✅ Process in batches
const batchSize = 100;
for (let i = 0; i < total; i += batchSize) {
  const batch = await loadBatch(i, batchSize);
  await processBatch(batch);
}

Deployment Failures

Symptoms: Deploy endpoint returns an error

Solutions:

  1. Verify code syntax is valid
  2. Ensure all dependencies are available in the runtime
  3. Check that runtime matches code language
  4. Review deployment error messages for specific issues

Event-Driven Scripts Not Executing

Symptoms: Events occur but script doesn't run

Solutions:

  1. Verify script is deployed (check deployment status)
  2. Confirm subscription is enabled
  3. Check event type matches subscription
  4. Verify conditions match event data
  5. Ensure daily execution limit hasn't been reached

Security

API Key Management

Each script has a unique API key for execution. The key is available in the execution context:

export async function handler(event, context) {
  // Access script API key
  const apiKey = context.apiKey;
  
  // Use for authenticated external calls
  const response = await fetch('https://api.example.com/data', {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    }
  });
  
  return { success: true };
}

Note: The script API key is different from your user API key. Script keys are scoped to individual scripts and cannot access other user resources.

Resource Access Control

Scripts can only access resources explicitly whitelisted in the allowedResources configuration. This prevents unauthorized access to sensitive APIs and credentials.

{
  "config": {
    "allowedResources": ["resource_abc123", "resource_def456"]
  }
}

Attempts to call non-whitelisted resources fail with a permission error.

User Isolation

Scripts execute only for their owner's events:

  • ✅ Your script receives your Telegram messages
  • ❌ Your script cannot receive other users' messages
  • ✅ Your script accesses your calendar events
  • ❌ Your script cannot access other users' calendars

Cost Management

Understanding Costs

Script execution costs include:

  • Compute time - Based on memory allocation and execution duration
  • Resource calls - API calls made through resources
  • Storage - Logs and execution history

Typical costs:

  • Simple script (128MB, <1s): ~$0.0001 per execution
  • Complex script (512MB, 10s): ~$0.001 per execution
  • Resource-heavy script: $0.01+ per execution

Setting Budgets

Control costs with execution and cost limits:

{
  "config": {
    "maxCostPerExecution": 0.01,
    "maxExecutionsPerDay": 1000
  }
}

Scripts stop executing when either limit is reached.

Monitoring Costs

Track spending through metrics:

curl https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/metrics \
  -H "X-API-Key: YOUR_API_KEY"

Review totalCost and avgDuration to identify expensive scripts.


Publishing to Marketplace

Make Script Public

Publish a script to the marketplace:

curl -X POST https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID/publish \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pricing": {
      "model": "free"
    }
  }'

Pricing Models

Free:

{
  "pricing": {
    "model": "free"
  }
}

Pay-Per-Execution:

{
  "pricing": {
    "model": "pay-per-execution",
    "basePrice": 0.01,
    "currency": "USDC"
  }
}

Subscription:

{
  "pricing": {
    "model": "subscription",
    "basePrice": 9.99,
    "currency": "USDC",
    "billingPeriod": "monthly"
  }
}

Private Scripts

Keep scripts private (not listed in marketplace):

curl -X PATCH https://api.getmirra.app/api/sdk/v1/scripts/SCRIPT_ID \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"isPrivate": true}'

See Also

On this page