inbox-zero icon indicating copy to clipboard operation
inbox-zero copied to clipboard

Create email provider adapter layer (for Outlook)

Open elie222 opened this issue 11 months ago • 2 comments

This should only be done after #271 and #272 are completed.

Create a common interface for email operations that works with both Gmail and Outlook.

Example Implementation

This was written by an LLM to give you an idea of how this could be approached:

// types.ts
interface EmailMessage {
  id: string;
  threadId: string; // Gmail threadId or Outlook conversationId
  subject: string;
  snippet: string;
  date: Date;
}

interface EmailProvider {
  // Core methods we need for MVP
  getMessage(messageId: string): Promise<EmailMessage>;
  listMessages(options: {
    folderId?: string, // Label in Gmail, Folder in Outlook
    pageToken?: string,
    maxResults?: number
  }): Promise<{
    messages: EmailMessage[],
    nextPageToken?: string
  }>;
  // Add more as needed
}

// gmail-provider.ts
class GmailProvider implements EmailProvider {
  constructor(private gmail: gmail_v1.Gmail) {}

  async getMessage(messageId: string): Promise<EmailMessage> {
    const msg = await this.gmail.users.messages.get({
      userId: 'me',
      id: messageId
    });
    
    return {
      id: msg.data.id!,
      threadId: msg.data.threadId!,
      subject: getSubject(msg.data), // Helper to parse Gmail headers
      snippet: msg.data.snippet!,
      date: new Date(getDate(msg.data)) // Helper to parse Gmail headers
    };
  }

  async listMessages(options: {
    folderId?: string,
    pageToken?: string,
    maxResults?: number
  }) {
    const response = await this.gmail.users.messages.list({
      userId: 'me',
      labelIds: options.folderId ? [options.folderId] : undefined,
      maxResults: options.maxResults,
      pageToken: options.pageToken
    });

    const messages = await Promise.all(
      response.data.messages!.map(m => this.getMessage(m.id!))
    );

    return {
      messages,
      nextPageToken: response.data.nextPageToken
    };
  }
}

// outlook-provider.ts
class OutlookProvider implements EmailProvider {
  constructor(private graph: Client) {}

  async getMessage(messageId: string): Promise<EmailMessage> {
    const msg = await this.graph.api(`/me/messages/${messageId}`)
      .select('id,conversationId,subject,bodyPreview,receivedDateTime')
      .get();
    
    return {
      id: msg.id,
      threadId: msg.conversationId, // Using conversationId as threadId
      subject: msg.subject,
      snippet: msg.bodyPreview,
      date: new Date(msg.receivedDateTime)
    };
  }

  async listMessages(options: {
    folderId?: string,
    pageToken?: string,
    maxResults?: number
  }) {
    let request = this.graph.api('/me/messages');
    
    if (options.folderId) {
      request = this.graph.api(`/me/mailFolders/${options.folderId}/messages`);
    }

    const response = await request
      .select('id,conversationId,subject,bodyPreview,receivedDateTime')
      .top(options.maxResults || 50)
      .skipToken(options.pageToken)
      .get();

    return {
      messages: response.value.map(msg => ({
        id: msg.id,
        threadId: msg.conversationId,
        subject: msg.subject,
        snippet: msg.bodyPreview,
        date: new Date(msg.receivedDateTime)
      })),
      nextPageToken: response['@odata.nextLink']
    };
  }
}

// factory.ts
function createEmailProvider(type: 'gmail' | 'outlook', client: any): EmailProvider {
  if (type === 'gmail') {
    return new GmailProvider(client);
  }
  return new OutlookProvider(client);
}

// Usage example
const provider = createEmailProvider(session.provider as 'gmail' | 'outlook', client);
const messages = await provider.listMessages({ maxResults: 10 });

Tasks

  1. [ ] Create EmailMessage and EmailProvider interfaces
  2. [ ] Implement GmailProvider
  3. [ ] Implement OutlookProvider
  4. [ ] Add factory function
  5. [ ] Test basic operations (get/list messages)
  6. [ ] Add webhook handling

Done when

  • Can switch between Gmail/Outlook without changing app code
  • Core email operations working with both providers
  • Webhook endpoints handling both providers

elie222 avatar Dec 19 '24 10:12 elie222

Please also support normal standards like json imap Gmail and MS also support imap...

celevra avatar Apr 11 '25 15:04 celevra

Being worked on here: https://github.com/elie222/inbox-zero/pull/493

elie222 avatar Jun 19 '25 19:06 elie222