# Front Client

Manage conversations, contacts, and team inboxes with Front's collaborative email platform.

## Methods

| Method                                   | Description                                       |
| ---------------------------------------- | ------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Front API request with schema validation |

## Usage

### Get Conversation

```typescript
import { api, z, front } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_FRONT = "a1b2c3d4-5678-90ab-cdef-front0000001";

const ConversationResponseSchema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  id: z.string(),
  subject: z.string(),
  status: z.enum(["archived", "deleted", "open", "spam"]),
  assignee: z
    .object({
      id: z.string(),
      email: z.string(),
    })
    .nullable(),
  recipient: z.object({
    handle: z.string(),
    role: z.string(),
  }),
  tags: z.array(z.object({ id: z.string(), name: z.string() })),
  created_at: z.number(),
  is_private: z.boolean(),
});

export default api({
  integrations: {
    front: front(PROD_FRONT),
  },
  name: "FrontExample",
  input: z.object({
    conversationId: z.string(),
  }),
  output: z.object({
    subject: z.string(),
    status: z.string(),
  }),
  async run(ctx, { conversationId }) {
    const result = await ctx.integrations.front.apiRequest(
      {
        method: "GET",
        path: `/conversations/${conversationId}`,
      },
      { response: ConversationResponseSchema },
    );

    return { subject: result.subject, status: result.status };
  },
});
```

### Send a Reply

```typescript
const MessageResponseSchema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  id: z.string(),
  type: z.string(),
  is_inbound: z.boolean(),
  created_at: z.number(),
  body: z.string(),
  author: z
    .object({
      id: z.string(),
      email: z.string(),
    })
    .nullable(),
});

const result = await ctx.integrations.front.apiRequest(
  {
    method: "POST",
    path: `/conversations/${conversationId}/messages`,
    body: {
      author_id: "tea_123", // Teammate ID
      body: "Thank you for reaching out! Let me help you with that.",
      options: {
        archive: false,
      },
    },
  },
  { response: MessageResponseSchema },
);
```

### Create a New Conversation

```typescript
const result = await ctx.integrations.front.apiRequest(
  {
    method: "POST",
    path: `/channels/${channelId}/messages`,
    body: {
      to: ["customer@example.com"],
      subject: "Following up on your inquiry",
      body: "Hi! I wanted to follow up on your recent question...",
      author_id: "tea_123",
      options: {
        tags: ["tag_123"],
      },
    },
  },
  { response: MessageResponseSchema },
);
```

### List Conversations in Inbox

```typescript
const ListConversationsResponseSchema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  _results: z.array(ConversationResponseSchema),
  _pagination: z.object({
    next: z.string().nullable(),
  }),
});

const result = await ctx.integrations.front.apiRequest(
  {
    method: "GET",
    path: `/inboxes/${inboxId}/conversations`,
    params: {
      q: "status:open", // Search query
      limit: 25,
    },
  },
  { response: ListConversationsResponseSchema },
);

result._results.forEach((conv) => {
  console.log(`${conv.subject} - ${conv.status}`);
});
```

### Add a Comment (Internal Note)

```typescript
const CommentResponseSchema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  id: z.string(),
  body: z.string(),
  author: z.object({
    id: z.string(),
    email: z.string(),
  }),
  created_at: z.number(),
});

const result = await ctx.integrations.front.apiRequest(
  {
    method: "POST",
    path: `/conversations/${conversationId}/comments`,
    body: {
      author_id: "tea_123",
      body: "Internal note: Customer is a VIP, handle with care.",
    },
  },
  { response: CommentResponseSchema },
);
```

### Assign Conversation

```typescript
await ctx.integrations.front.apiRequest(
  {
    method: "PATCH",
    path: `/conversations/${conversationId}`,
    body: {
      assignee_id: "tea_456", // Teammate to assign to
      status: "open",
    },
  },
  { response: ConversationResponseSchema },
);
```

### Add Tags to Conversation

```typescript
await ctx.integrations.front.apiRequest(
  {
    method: "POST",
    path: `/conversations/${conversationId}/tags`,
    body: {
      tag_ids: ["tag_123", "tag_456"],
    },
  },
  { response: z.object({}).optional() },
);
```

### Get Contact

```typescript
const ContactResponseSchema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  id: z.string(),
  name: z.string(),
  description: z.string(),
  handles: z.array(
    z.object({
      handle: z.string(),
      source: z.string(),
    }),
  ),
  groups: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
    }),
  ),
  custom_fields: z.record(z.unknown()),
});

const result = await ctx.integrations.front.apiRequest(
  {
    method: "GET",
    path: `/contacts/${contactId}`,
  },
  { response: ContactResponseSchema },
);
```

### Create Contact

```typescript
const result = await ctx.integrations.front.apiRequest(
  {
    method: "POST",
    path: "/contacts",
    body: {
      name: "John Doe",
      description: "Enterprise customer",
      handles: [
        { handle: "john@company.com", source: "email" },
        { handle: "+15551234567", source: "phone" },
      ],
      custom_fields: {
        company: "Acme Corp",
        plan: "enterprise",
      },
    },
  },
  { response: ContactResponseSchema },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await front.sendMessage({ ... });
await front.createConversation({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.front.apiRequest(
  { method: "POST", path: `/conversations/${id}/messages`, body: { ... } },
  { response: MessageResponseSchema }
);
```

### ID Prefixes

Front uses prefixes for different resource types:

```typescript
// Teammate IDs start with "tea_"
const teammateId = "tea_123abc";

// Inbox IDs start with "inb_"
const inboxId = "inb_456def";

// Tag IDs start with "tag_"
const tagId = "tag_789ghi";

// Channel IDs start with "cha_"
const channelId = "cha_012jkl";

// Conversation IDs start with "cnv_"
const conversationId = "cnv_345mno";
```

### Author ID Required for Messages

When sending messages, you must specify the author:

```typescript
// WRONG - Missing author_id
const body = {
  body: "Reply text",
};

// CORRECT - Include author_id
const body = {
  author_id: "tea_123", // Teammate ID
  body: "Reply text",
};
```

### Response Format

Front uses `_links`, `_results`, and `_pagination`:

```typescript
const schema = z.object({
  _links: z.object({
    self: z.string(),
  }),
  _results: z.array(ItemSchema),
  _pagination: z.object({
    next: z.string().nullable(),
  }),
});
```

### Handles for Contacts

Contacts can have multiple handles (email, phone, etc.):

```typescript
const contact = {
  handles: [
    { handle: "user@example.com", source: "email" },
    { handle: "@username", source: "twitter" },
    { handle: "+15551234567", source: "phone" },
  ],
};
```

### Search Query Syntax

Use Front's query syntax for filtering:

```typescript
const params = {
  q: "status:open assignee:tea_123", // Multiple filters
};

// Available filters:
// status:open/archived/deleted/spam
// assignee:tea_xxx
// tag:tag_xxx
// inbox:inb_xxx
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.front.apiRequest(
    { method: "GET", path: `/conversations/${id}` },
    { response: ConversationResponseSchema },
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Front API Documentation](https://dev.frontapp.com/reference/introduction)
- [Conversations](https://dev.frontapp.com/reference/conversations)
- [Messages](https://dev.frontapp.com/reference/messages)
- [Contacts](https://dev.frontapp.com/reference/contacts)
- [Inboxes](https://dev.frontapp.com/reference/inboxes)
