Skip to main content

Overview

Follow these best practices to build reliable, efficient, and maintainable applications with the Advanced iMessage Kit SDK.

Connection Management

Always Wait for Ready

Never send messages or make API calls before the SDK is ready.
const sdk = new AdvancedIMessageKit({
  serverUrl: "http://localhost:1234",
  apiKey: "your-api-key"
});

sdk.on("ready", async () => {
  // Safe to use SDK now
  await sdk.messages.sendMessage({
    chatGuid: CHAT_GUID,
    message: "Hello!"
  });
});

await sdk.connect();

Graceful Shutdown

Always close the SDK connection when your application exits.
process.on("SIGINT", async () => {
  console.log("\nShutting down gracefully...");
  await sdk.close();
  process.exit(0);
});

process.on("SIGTERM", async () => {
  console.log("\nShutting down gracefully...");
  await sdk.close();
  process.exit(0);
});

Monitor Connection State

Handle disconnections and reconnections gracefully.
sdk.on("disconnect", () => {
  console.log("Disconnected - SDK will auto-reconnect");
  // Pause operations if needed
});

sdk.on("ready", () => {
  console.log("Connected and ready");
  // Resume operations
});

sdk.socket.io.on("reconnect", (attempt) => {
  console.log(`Reconnected after ${attempt} attempt(s)`);
});
The SDK automatically reconnects with exponential backoff. You don’t need to manually reconnect.

Message Deduplication

Understanding Built-in Deduplication

The SDK automatically deduplicates new-message events using message GUIDs.
// The SDK maintains an internal set of processed message GUIDs
// Duplicate messages are automatically filtered

sdk.on("new-message", (message) => {
  // This will only fire once per unique message.guid
  console.log(`New message: ${message.text}`);
});

Managing Memory in Long-Running Apps

Clear processed message records periodically to prevent memory leaks.
// Clear old records every hour
setInterval(() => {
  const beforeCount = sdk.getProcessedMessageCount();
  sdk.clearProcessedMessages(1000); // Keep last 1000 messages
  const afterCount = sdk.getProcessedMessageCount();
  
  console.log(`Cleared ${beforeCount - afterCount} old message records`);
}, 60 * 60 * 1000);

Avoiding Reply Loops

Always check isFromMe to prevent auto-reply loops.
sdk.on("new-message", async (message) => {
  // Skip our own messages
  if (message.isFromMe) return;
  
  // Safe to auto-reply
  await sdk.messages.sendMessage({
    chatGuid: message.chats[0].guid,
    message: "Auto-reply"
  });
});

Error Handling

Always Use Try-Catch

Wrap SDK calls in try-catch blocks.
try {
  const message = await sdk.messages.sendMessage({
    chatGuid: CHAT_GUID,
    message: "Hello!"
  });
  console.log("Message sent:", message.guid);
} catch (error) {
  console.error("Failed to send message:", error.message);
  
  // Handle specific errors
  if (error.response?.status === 404) {
    console.error("Chat not found");
  } else if (error.response?.status === 429) {
    console.error("Rate limited");
  }
}

Listen for Error Events

Monitor SDK-level errors.
sdk.on("error", (error) => {
  console.error("SDK Error:", error.message);
  
  // Log to error tracking service
  // Sentry.captureException(error);
});

sdk.on("message-send-error", (message) => {
  console.error(`Failed to send: ${message.text}`);
  console.error(`Error code: ${message.error}`);
});

Validate Inputs

Validate data before sending.
async function sendMessage(chatGuid: string, text: string) {
  // Validate inputs
  if (!chatGuid) {
    throw new Error("chatGuid is required");
  }
  
  if (!text || text.trim().length === 0) {
    throw new Error("Message text cannot be empty");
  }
  
  if (text.length > 10000) {
    throw new Error("Message text too long");
  }
  
  // Send message
  return await sdk.messages.sendMessage({ chatGuid, message: text });
}

Resource Management

Sequential Message Delivery

The SDK automatically queues messages for sequential delivery.
// These will be sent in order automatically
const promises = [
  sdk.messages.sendMessage({ chatGuid: CHAT_GUID, message: "First" }),
  sdk.messages.sendMessage({ chatGuid: CHAT_GUID, message: "Second" }),
  sdk.messages.sendMessage({ chatGuid: CHAT_GUID, message: "Third" })
];

// Wait for all to complete
await Promise.all(promises);
The SDK uses an internal queue (enqueueSend) to ensure messages are sent sequentially, preventing race conditions.

File Cleanup

Clean up temporary files after sending attachments.
import fs from "fs";
import path from "path";

const tempFile = path.join("/tmp", "temp-image.jpg");

try {
  // Send attachment
  await sdk.attachments.sendAttachment({
    chatGuid: CHAT_GUID,
    filePath: tempFile
  });
  
  console.log("Attachment sent");
} finally {
  // Clean up
  if (fs.existsSync(tempFile)) {
    fs.unlinkSync(tempFile);
  }
}

Limit Concurrent Operations

Avoid overwhelming the server with too many concurrent requests.
import pLimit from "p-limit";

// Limit to 5 concurrent downloads
const limit = pLimit(5);

const attachments = [/* array of attachment GUIDs */];

const downloads = attachments.map(guid => 
  limit(async () => {
    const buffer = await sdk.attachments.downloadAttachment(guid);
    return { guid, buffer };
  })
);

const results = await Promise.all(downloads);

Performance Optimization

Batch API Calls

When fetching data, use pagination and limits.
// Good - fetch in batches
const messages = await sdk.messages.getMessages({
  chatGuid: CHAT_GUID,
  limit: 50,
  offset: 0
});

// Bad - fetching all messages at once (slow)
const allMessages = await sdk.messages.getMessages({
  chatGuid: CHAT_GUID
  // No limit = potentially thousands of messages
});

Cache Chat Data

Cache frequently accessed chat information.
const chatCache = new Map();

async function getChatWithCache(chatGuid: string) {
  if (chatCache.has(chatGuid)) {
    return chatCache.get(chatGuid);
  }
  
  const chat = await sdk.chats.getChat(chatGuid);
  chatCache.set(chatGuid, chat);
  
  // Clear cache after 5 minutes
  setTimeout(() => chatCache.delete(chatGuid), 5 * 60 * 1000);
  
  return chat;
}

Use Selective Data Loading

Only fetch data you need.
// Good - only fetch attachments when needed
const messages = await sdk.messages.getMessages({
  chatGuid: CHAT_GUID,
  limit: 50,
  with: ["attachment"] // Only include attachments
});

// Bad - fetching all related data unnecessarily
const messagesWithEverything = await sdk.messages.getMessages({
  chatGuid: CHAT_GUID,
  limit: 50,
  with: ["attachment", "attributedBody", "messageSummaryInfo", "payloadData"]
});

Logging and Debugging

Configure Log Levels

Set appropriate log levels for different environments.
const sdk = new AdvancedIMessageKit({
  serverUrl: "http://localhost:1234",
  apiKey: "your-api-key",
  logLevel: process.env.NODE_ENV === "production" ? "error" : "debug",
  logToFile: true
});
Available log levels:
  • debug - Verbose logging for development
  • info - Standard operational messages
  • warn - Warning messages
  • error - Error messages only

Implement Structured Logging

Log events in a structured format.
sdk.on("new-message", (message) => {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    event: "new-message",
    messageGuid: message.guid,
    sender: message.handle?.address,
    chatGuid: message.chats?.[0]?.guid,
    hasAttachments: !!message.attachments?.length,
    isFromMe: message.isFromMe
  }));
});

Security Best Practices

Protect API Keys

Never hardcode API keys in your source code.
import dotenv from "dotenv";
dotenv.config();

const sdk = new AdvancedIMessageKit({
  serverUrl: process.env.SERVER_URL,
  apiKey: process.env.API_KEY
});

Validate Chat GUIDs

Sanitize and validate chat GUIDs from user input.
function isValidChatGuid(guid: string): boolean {
  // Chat GUIDs follow patterns like:
  // - "iMessage;-;+1234567890"
  // - "iMessage;+;chat123456"
  // - "SMS;-;+1234567890"
  
  return /^(iMessage|SMS);[\-+];.+$/.test(guid);
}

const userProvidedGuid = getUserInput();

if (!isValidChatGuid(userProvidedGuid)) {
  throw new Error("Invalid chat GUID");
}

Sanitize Message Content

Validate and sanitize message content.
function sanitizeMessage(text: string): string {
  // Remove null bytes
  let sanitized = text.replace(/\0/g, "");
  
  // Trim whitespace
  sanitized = sanitized.trim();
  
  // Limit length
  if (sanitized.length > 10000) {
    sanitized = sanitized.substring(0, 10000);
  }
  
  return sanitized;
}

const userMessage = sanitizeMessage(getUserInput());

await sdk.messages.sendMessage({
  chatGuid: CHAT_GUID,
  message: userMessage
});

Testing

Mock the SDK in Tests

Use dependency injection for testability.
import { AdvancedIMessageKit } from "@photon-ai/advanced-imessage-kit";

class MessageService {
  constructor(private sdk: AdvancedIMessageKit) {}
  
  async sendWelcome(chatGuid: string) {
    return await this.sdk.messages.sendMessage({
      chatGuid,
      message: "Welcome!"
    });
  }
}

// In tests
const mockSdk = {
  messages: {
    sendMessage: jest.fn().mockResolvedValue({ guid: "test-guid" })
  }
};

const service = new MessageService(mockSdk as any);
await service.sendWelcome("test-chat");

expect(mockSdk.messages.sendMessage).toHaveBeenCalledWith({
  chatGuid: "test-chat",
  message: "Welcome!"
});

Test Error Scenarios

Test how your app handles errors.
test("handles message send failure", async () => {
  const mockSdk = {
    messages: {
      sendMessage: jest.fn().mockRejectedValue(new Error("Network error"))
    }
  };
  
  const service = new MessageService(mockSdk as any);
  
  await expect(service.sendWelcome("test-chat")).rejects.toThrow("Network error");
});

Monitoring and Observability

Track Message Metrics

let messagesSent = 0;
let messagesFailed = 0;

sdk.on("new-message", (message) => {
  if (message.isFromMe) {
    messagesSent++;
  }
});

sdk.on("message-send-error", () => {
  messagesFailed++;
});

// Report metrics every minute
setInterval(() => {
  console.log(`Messages sent: ${messagesSent}`);
  console.log(`Messages failed: ${messagesFailed}`);
  console.log(`Success rate: ${(messagesSent / (messagesSent + messagesFailed) * 100).toFixed(2)}%`);
}, 60 * 1000);

Monitor Connection Health

let reconnectCount = 0;
let lastDisconnect: Date | null = null;

sdk.on("disconnect", () => {
  lastDisconnect = new Date();
});

sdk.socket.io.on("reconnect", () => {
  reconnectCount++;
  
  if (lastDisconnect) {
    const downtime = Date.now() - lastDisconnect.getTime();
    console.log(`Downtime: ${downtime}ms`);
  }
});

Common Pitfalls

Don’t Block the Event Loop

sdk.on("new-message", async (message) => {
  // Use async/await properly
  try {
    await processMessage(message);
  } catch (error) {
    console.error(error);
  }
});

Don’t Ignore Attachment Limits

import fs from "fs";

const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB

async function sendAttachment(filePath: string) {
  const stats = fs.statSync(filePath);
  
  if (stats.size > MAX_FILE_SIZE) {
    throw new Error("File too large");
  }
  
  return await sdk.attachments.sendAttachment({
    chatGuid: CHAT_GUID,
    filePath
  });
}

Don’t Assume Message Order

While the SDK queues sends, received messages may arrive out of order.
sdk.on("new-message", (message) => {
  // Sort by timestamp if order matters
  messages.push(message);
  messages.sort((a, b) => a.dateCreated - b.dateCreated);
});

Production Checklist

1

Environment Configuration

  • API keys stored in environment variables
  • Log level set appropriately
  • Server URL configurable
2

Error Handling

  • All SDK calls wrapped in try-catch
  • Error events monitored
  • Graceful degradation implemented
3

Resource Management

  • Connection properly closed on exit
  • Processed messages cleared periodically
  • Temporary files cleaned up
4

Monitoring

  • Metrics tracked and logged
  • Connection health monitored
  • Error tracking service integrated
5

Testing

  • Unit tests for message handling
  • Error scenarios tested
  • Integration tests for key flows

Next Steps

API Reference

Explore the complete API documentation

Examples

Browse example implementations

Build docs developers (and LLMs) love