Blog

Costruire una Casella di Posta Unificata per i Social Media con le API

Scopri come costruire una casella di posta unificata per i social media che aggrega i messaggi da Facebook, Instagram, Twitter e altro in un'unica interfaccia API.

Di

+8

Pubblica ovunque. Una API.

Try Free

Gestire le conversazioni con i clienti su Facebook, Instagram, Twitter, Bluesky, Reddit e Telegram richiede un continuo cambio di contesto. Una casella di posta unificata per i social media risolve questo problema aggregando tutti i messaggi in un'unica interfaccia, permettendo al tuo team di rispondere più velocemente senza saltare tra le piattaforme.

Questa guida illustra come costruire una casella di posta multi-piattaforma da zero. Imparerai come progettare il modello dati, normalizzare i messaggi da diverse API, gestire i webhook in tempo reale e implementare la paginazione basata su cursori che funziona su tutte le piattaforme. Alla fine, avrai un'architettura pronta per la produzione per aggregare i messaggi social su larga scala.

La Sfida della Messaggistica Multi-Piattaforma

Ogni piattaforma social ha la propria API di messaggistica con flussi di autenticazione unici, strutture dati, limiti di frequenza e formati webhook. Ecco cosa devi affrontare:

Facebook e Instagram utilizzano la Messenger Platform API con ID utente con scope a livello di pagina. I messaggi arrivano tramite webhook, e hai bisogno di token di accesso separati per ogni account connesso.

Twitter/X fornisce la Direct Messages API, ma l'accesso richiede livelli API elevati. Il polling dei messaggi è il metodo principale poiché il supporto webhook è limitato.

Bluesky utilizza l'AT Protocol con un'architettura decentralizzata. I messaggi diretti funzionano diversamente rispetto alle piattaforme tradizionali, richiedendo query basate su lexicon.

Reddit offre messaggistica privata attraverso la loro API, ma i limiti di frequenza sono aggressivi (60 richieste al minuto per i client OAuth).

Telegram fornisce la Bot API con long polling o webhook. Ogni bot ottiene il proprio token, e i messaggi includono metadati ricchi sui tipi di chat.

Le sfide principali quando si costruisce un'API per casella di posta social media includono:

  1. Complessità dell'autenticazione: Ogni piattaforma richiede diversi flussi OAuth, strategie di refresh dei token e scope di permessi
  2. Normalizzazione dei dati: Un "messaggio" significa qualcosa di diverso su ogni piattaforma
  3. Consegna in tempo reale: I formati webhook variano enormemente, e alcune piattaforme non supportano affatto i webhook
  4. Gestione dei limiti di frequenza: Raggiungere i limiti su una piattaforma non dovrebbe bloccare l'intera casella di posta
  5. Inconsistenza della paginazione: Alcune usano cursori, altre offset, e la paginazione basata su timestamp si comporta diversamente ovunque

Panoramica dell'Architettura per una Casella di Posta Unificata per Social Media

Una casella di posta multi-piattaforma ben progettata separa le responsabilità in livelli distinti:

Architettura Casella di Posta Unificata

// Architecture layers for a unified inbox system

interface InboxArchitecture {
  // Layer 1: Platform Adapters
  adapters: {
    facebook: FacebookAdapter;
    instagram: InstagramAdapter;
    twitter: TwitterAdapter;
    bluesky: BlueskyAdapter;
    reddit: RedditAdapter;
    telegram: TelegramAdapter;
  };
  
  // Layer 2: Normalization Engine
  normalizer: {
    transformMessage: (platformMessage: unknown, platform: string) => NormalizedMessage;
    transformConversation: (platformConvo: unknown, platform: string) => NormalizedConversation;
  };
  
  // Layer 3: Aggregation Service
  aggregator: {
    fetchAllConversations: (accounts: SocialAccount[]) => Promise<AggregatedResult<Conversation>>;
    fetchAllMessages: (conversationIds: string[]) => Promise<AggregatedResult<Message>>;
  };
  
  // Layer 4: Storage Layer
  storage: {
    conversations: ConversationRepository;
    messages: MessageRepository;
    accounts: AccountRepository;
  };
  
  // Layer 5: API Layer
  api: {
    getConversations: (filters: ConversationFilters) => Promise<PaginatedResponse<Conversation>>;
    getMessages: (conversationId: string, cursor?: string) => Promise<PaginatedResponse<Message>>;
    sendMessage: (conversationId: string, content: MessageContent) => Promise<Message>;
  };
}

Il pattern adapter isola la logica specifica della piattaforma. Quando Instagram cambia la loro API, aggiorni un solo adapter senza toccare il resto del tuo codice.

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

Piattaforme Supportate e le Loro API

Non tutte le piattaforme supportano tutte le funzionalità della casella di posta. Ecco un approccio guidato dalla configurazione per gestire le capacità delle piattaforme:

// Platform support configuration for inbox features

export type InboxFeature = 'messages' | 'comments' | 'reviews';

export const INBOX_PLATFORMS = {
  messages: ['facebook', 'instagram', 'twitter', 'bluesky', 'reddit', 'telegram'] as const,
  comments: ['facebook', 'instagram', 'twitter', 'bluesky', 'threads', 'youtube', 'linkedin', 'reddit'] as const,
  reviews: ['facebook', 'googlebusiness'] as const,
} as const;

export type MessagesPlatform = (typeof INBOX_PLATFORMS.messages)[number];
export type CommentsPlatform = (typeof INBOX_PLATFORMS.comments)[number];
export type ReviewsPlatform = (typeof INBOX_PLATFORMS.reviews)[number];

// Check if a platform supports a specific feature
export function isPlatformSupported(platform: string, feature: InboxFeature): boolean {
  return (INBOX_PLATFORMS[feature] as readonly string[]).includes(platform);
}

// Validate and return helpful error messages
export function validatePlatformSupport(
  platform: string,
  feature: InboxFeature
): { valid: true } | { valid: false; error: string; supportedPlatforms: readonly string[] } {
  if (!isPlatformSupported(platform, feature)) {
    const featureLabel = feature === 'messages' ? 'direct messages' : feature;
    return {
      valid: false,
      error: `Platform '${platform}' does not support ${featureLabel}`,
      supportedPlatforms: INBOX_PLATFORMS[feature],
    };
  }
  return { valid: true };
}

Nota: TikTok e Pinterest sono notevolmente assenti dalle liste di messaggi e commenti. Le loro API non forniscono accesso in lettura ai messaggi o commenti degli utenti, rendendole inadatte per l'aggregazione della casella di posta.

Ecco un confronto delle capacità API tra le piattaforme:

PiattaformaMessaggiCommentiRecensioniWebhookLimiti di Frequenza
Facebook200/ora/utente
Instagram200/ora/utente
TwitterLimitato15/15min (DM)
Bluesky3000/5min
Reddit60/min
Telegram30/sec
LinkedIn✅ (Org)100/giorno
YouTube10000/giorno

Progettazione del Modello Dati per Conversazioni e Messaggi

Il tuo modello dati deve gestire l'unione di tutte le funzionalità delle piattaforme mantenendo relazioni pulite. Ecco uno schema MongoDB che funziona bene:

// Conversation schema - represents a thread with a participant

import mongoose from "mongoose";

const conversationSchema = new mongoose.Schema(
  {
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
      index: true,
    },
    accountId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "SocialAccount",
      required: true,
      index: true,
    },
    profileId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Profile",
      required: false,
    },
    platform: {
      type: String,
      enum: ["instagram", "facebook", "telegram", "twitter", "bluesky", "reddit"],
      required: true,
      index: true,
    },
    // Platform's native conversation identifier
    platformConversationId: {
      type: String,
      required: true,
      index: true,
    },
    status: {
      type: String,
      enum: ["active", "archived"],
      default: "active",
      index: true,
    },
    // Cached participant info for fast display
    participantName: String,
    participantUsername: String,
    participantPicture: String,
    participantId: String,
    // Preview data for inbox list view
    lastMessage: String,
    lastMessageAt: {
      type: Date,
      index: true,
    },
    // Platform-specific extras
    metadata: {
      type: Map,
      of: mongoose.Schema.Types.Mixed,
    },
  },
  { timestamps: true }
);

// Prevent duplicate conversations per account
conversationSchema.index(
  { accountId: 1, platformConversationId: 1 }, 
  { unique: true }
);

// Optimize inbox list queries
conversationSchema.index(
  { accountId: 1, status: 1, lastMessageAt: -1 }
);

Lo schema dei messaggi cattura l'intera gamma di tipi di contenuto:

// Message schema - individual messages within conversations

const messageSchema = new mongoose.Schema(
  {
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
      index: true,
    },
    accountId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "SocialAccount",
      required: true,
      index: true,
    },
    conversationId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Conversation",
      required: true,
      index: true,
    },
    platform: {
      type: String,
      enum: ["instagram", "facebook", "telegram", "twitter", "bluesky", "reddit"],
      required: true,
      index: true,
    },
    platformMessageId: {
      type: String,
      required: true,
      index: true,
    },
    direction: {
      type: String,
      enum: ["incoming", "outgoing"],
      required: true,
      index: true,
    },
    senderId: {
      type: String,
      required: true,
    },
    senderName: String,
    senderPicture: String,
    text: String,
    attachments: [{
      type: {
        type: String,
        enum: ["image", "video", "audio", "file", "sticker", "share"],
      },
      url: String,
      payload: mongoose.Schema.Types.Mixed,
    }],
    // Instagram story interactions
    storyReply: {
      storyId: String,
      storyUrl: String,
    },
    isStoryMention: {
      type: Boolean,
      default: false,
    },
    platformTimestamp: {
      type: Date,
      required: true,
      index: true,
    },
    isRead: {
      type: Boolean,
      default: false,
    },
    autoResponseSent: {
      type: Boolean,
      default: false,
    },
    // Keep raw data for debugging
    rawPayload: mongoose.Schema.Types.Mixed,
  },
  { timestamps: true }
);

// Prevent duplicate messages
messageSchema.index(
  { accountId: 1, platformMessageId: 1 }, 
  { unique: true }
);

// Chronological message fetching
messageSchema.index(
  { conversationId: 1, platformTimestamp: -1 }
);

// Unread count queries
messageSchema.index(
  { conversationId: 1, direction: 1, isRead: 1 }
);

Il campo direction è critico. Distingue tra i messaggi inviati dal tuo team (outgoing) e i messaggi dai clienti (incoming). Questo alimenta funzionalità come il conteggio dei non letti e le analitiche sui tempi di risposta.

Strategia di Aggregazione e Normalizzazione

Il livello di aggregazione recupera i dati da più account in parallelo gestendo con grazia i fallimenti. Ecco l'implementazione principale:

// Aggregation utilities for multi-account data fetching

export interface AggregationError {
  accountId: string;
  accountUsername?: string;
  platform: string;
  error: string;
  code?: string;
  retryAfter?: number;
}

export interface AggregatedResult<T> {
  items: T[];
  errors: AggregationError[];
}

export async function aggregateFromAccounts<T>(
  accounts: SocialAccount[],
  fetcher: (account: SocialAccount) => Promise<T[]>,
  options?: { timeout?: number }
): Promise<AggregatedResult<T>> {
  const timeout = options?.timeout || 10000;
  const results: T[] = [];
  const errors: AggregationError[] = [];

  const fetchPromises = accounts.map(async (account) => {
    try {
      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error('Request timeout')), timeout);
      });

      const items = await Promise.race([fetcher(account), timeoutPromise]);
      return { account, items, error: null };
    } catch (error: unknown) {
      const err = error as Error & { code?: string; retryAfter?: number };
      return {
        account,
        items: [] as T[],
        error: {
          accountId: account._id?.toString() || account.id,
          accountUsername: account.username,
          platform: account.platform,
          error: err.message || 'Unknown error',
          code: err.code,
          retryAfter: err.retryAfter,
        },
      };
    }
  });

  const settledResults = await Promise.all(fetchPromises);

  for (const result of settledResults) {
    if (result.error) {
      errors.push(result.error);
    } else {
      results.push(...result.items);
    }
  }

  return { items: results, errors };
}

Questo pattern assicura che un limite di frequenza su Twitter non impedisca di mostrare i messaggi di Facebook. L'array errors permette al tuo frontend di visualizzare risultati parziali con avvisi appropriati.

Per normalizzare i dati specifici della piattaforma nel tuo formato unificato:

// Message normalization from platform-specific formats

interface NormalizedMessage {
  platformMessageId: string;
  platform: string;
  direction: 'incoming' | 'outgoing';
  senderId: string;
  senderName?: string;
  text?: string;
  attachments: Attachment[];
  platformTimestamp: Date;
}

function normalizeInstagramMessage(
  raw: InstagramMessage, 
  accountId: string
): NormalizedMessage {
  const isOutgoing = raw.from.id === accountId;
  
  return {
    platformMessageId: raw.id,
    platform: 'instagram',
    direction: isOutgoing ? 'o

Una API. 13+ piattaforme.

Integra i social in minuti, non settimane.

Progettato per sviluppatori. Apprezzato dalle agenzie. Fidato da 6.325 utenti.