import { CakemailAPI } from '../cakemail-api.js';
import { handleCakemailError } from '../utils/errors.js';
// Define Contact type locally to fix implicit any types
interface ContactExtended {
id: number;
email: string;
first_name?: string;
last_name?: string;
status?: string;
custom_fields?: Record<string, any>;
created_on?: string;
updated_on?: string;
subscribed_on?: string;
unsubscribed_on?: string;
bounced_on?: string;
tags?: string[];
}
export async function handleListContacts(args: any, api: CakemailAPI) {
try {
const {
list_id, page, per_page, email, status, sort, order, with_count, account_id
} = args;
const contacts = await api.contacts.getContacts({
list_id,
page: page || 1,
per_page: per_page || 50,
...(email && { email }),
...(status && { status }),
sort: sort || 'created_on',
order: (order as 'asc' | 'desc') || 'desc',
with_count: with_count !== false,
...(account_id !== undefined && { account_id })
});
const total = contacts.pagination?.count || 0;
const contactData = contacts.data?.slice(0, 20).map((contact: ContactExtended) => ({
id: contact.id,
email: contact.email,
first_name: contact.first_name,
last_name: contact.last_name,
status: contact.status,
custom_fields: contact.custom_fields,
created_on: contact.created_on,
updated_on: contact.updated_on,
unsubscribed_on: contact.unsubscribed_on,
bounced_on: contact.bounced_on,
tags: contact.tags
}));
return {
content: [
{
type: 'text',
text: `š„ **Contacts (${total} total)**\n\n` +
`**Applied Filters:**\n` +
`⢠List ID: ${list_id || 'all'}\n` +
`⢠Email Filter: ${email || 'none'}\n` +
`⢠Status Filter: ${status || 'all'}\n` +
`⢠Sort: ${sort || 'created_on'} (${order || 'desc'})\n\n` +
`**Showing ${contactData?.length || 0} contacts:**\n\n` +
(contactData?.map((contact, i) =>
`${i + 1}. **${contact.email}** (${contact.id})\n` +
` š¤ Name: ${contact.first_name || ''} ${contact.last_name || ''}\n` +
` š Status: ${contact.status || 'N/A'}\n` +
` š·ļø Tags: ${contact.tags?.join(', ') || 'None'}\n` +
` š
Created: ${contact.created_on || 'N/A'}\n` +
` š Updated: ${contact.updated_on || 'N/A'}\n` +
(contact.unsubscribed_on ? ` ā Unsubscribed: ${contact.unsubscribed_on}\n` : '') +
(contact.bounced_on ? ` ā ļø Bounced: ${contact.bounced_on}\n` : '')
).join('\n\n') || 'No contacts found.') +
(total > 20 ? `\n\n**... and ${total - 20} more contacts**` : '') +
`\n\n**Full Response:**\n${JSON.stringify(contacts, null, 2)}`,
},
],
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleCreateContact(args: any, api: CakemailAPI) {
try {
const {
list_id, email, first_name, last_name, custom_fields, status
} = args;
// Validate required fields
if (!list_id || !email) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Fields**\n\nRequired: list_id, email'
}]
};
}
const contactData: any = {
list_id,
email,
first_name,
last_name,
custom_fields,
status
};
const result = await api.contacts.createContact(contactData);
return {
content: [{
type: 'text',
text: `ā
**Contact Created Successfully**\n\n` +
`š¤ **Contact Details:**\n` +
`⢠ID: ${result.data?.id}\n` +
`⢠Email: ${email}\n` +
`⢠Name: ${first_name || ''} ${last_name || ''}\n` +
`⢠List ID: ${list_id}\n` +
`⢠Status: ${status || 'active'}\n` +
`⢠Custom Fields: ${custom_fields ? Object.keys(custom_fields).join(', ') : 'None'}\n\n` +
`**Full Response:**\n${JSON.stringify(result, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleGetContact(args: any, api: CakemailAPI) {
try {
const { contact_id } = args;
if (!contact_id) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Field**\n\nRequired: contact_id'
}]
};
}
const result = await api.contacts.getContact(contact_id);
const contact = result.data;
return {
content: [{
type: 'text',
text: `š¤ **Contact Details**\n\n` +
`**Basic Information:**\n` +
`⢠ID: ${contact?.id}\n` +
`⢠Email: ${contact?.email}\n` +
`⢠First Name: ${contact?.first_name || 'N/A'}\n` +
`⢠Last Name: ${contact?.last_name || 'N/A'}\n` +
`⢠Status: ${contact?.status}\n` +
`⢠Created: ${(contact as any)?.created_on || 'N/A'}\n` +
`⢠Updated: ${(contact as any)?.updated_on || 'N/A'}\n\n` +
`**Subscription Details:**\n` +
`⢠Subscribed: ${contact?.subscribed_on || 'N/A'}\n` +
`⢠Unsubscribed: ${(contact as any)?.unsubscribed_on || 'N/A'}\n` +
`⢠Bounced: ${(contact as any)?.bounced_on || 'N/A'}\n\n` +
`**Custom Fields:**\n` +
(contact?.custom_fields ?
Object.entries(contact.custom_fields).map(([key, value]) =>
`⢠${key}: ${value}`
).join('\n') || 'None' : 'None') +
`\n\n**Tags:**\n` +
((contact as any)?.tags?.length ? (contact as any).tags.join(', ') : 'None') +
`\n\n**Full Response:**\n${JSON.stringify(result, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleUpdateContact(args: any, api: CakemailAPI) {
try {
const {
contact_id, email, first_name, last_name, custom_fields, status
} = args;
if (!contact_id) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Field**\n\nRequired: contact_id'
}]
};
}
let updateData: any = {};
// Only include fields that are provided
if (email !== undefined) updateData.email = email;
if (first_name !== undefined) updateData.first_name = first_name;
if (last_name !== undefined) updateData.last_name = last_name;
if (custom_fields !== undefined) updateData.custom_fields = custom_fields;
if (status !== undefined) updateData.status = status;
if (Object.keys(updateData).length === 0) {
return {
content: [{
type: 'text',
text: 'ā **No Update Data**\n\nAt least one field must be provided for update.'
}]
};
}
const result = await api.contacts.updateContact(contact_id, updateData);
return {
content: [{
type: 'text',
text: `ā
**Contact Updated Successfully**\n\n` +
`š¤ **Contact Details:**\n` +
`⢠ID: ${contact_id}\n` +
`⢠Fields Updated: ${Object.keys(updateData).join(', ')}\n` +
(updateData.email ? `⢠New Email: ${updateData.email}\n` : '') +
(updateData.first_name !== undefined || updateData.last_name !== undefined ?
`⢠New Name: ${updateData.first_name || ''} ${updateData.last_name || ''}\n` : '') +
(updateData.status ? `⢠New Status: ${updateData.status}\n` : '') +
(updateData.custom_fields ? `⢠Custom Fields Updated: ${Object.keys(updateData.custom_fields).join(', ')}\n` : '') +
`\n**Full Response:**\n${JSON.stringify(result, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleDeleteContact(args: any, api: CakemailAPI) {
try {
const { contact_id } = args;
if (!contact_id) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Field**\n\nRequired: contact_id'
}]
};
}
const result = await api.contacts.deleteContact(contact_id);
return {
content: [{
type: 'text',
text: `ā
**Contact Deleted Successfully**\n\n` +
`š¤ **Deleted Contact:**\n` +
`⢠ID: ${contact_id}\n` +
`⢠Status: Permanently deleted\n\n` +
`ā ļø **Warning:** This action is permanent and cannot be undone.\n\n` +
`**Full Response:**\n${JSON.stringify(result, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleUnsubscribeContact(args: any, api: CakemailAPI) {
try {
const { list_id, contact_id } = args;
if (!list_id || !contact_id) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Fields**\n\nRequired: list_id, contact_id'
}]
};
}
// Using the update contact method to set status to unsubscribed
const updateData: any = { status: 'unsubscribed' };
const result = await api.contacts.updateContact(contact_id, updateData);
return {
content: [{
type: 'text',
text: `ā
**Contact Unsubscribed Successfully**\n\n` +
`š¤ **Contact Details:**\n` +
`⢠ID: ${contact_id}\n` +
`⢠List ID: ${list_id}\n` +
`⢠New Status: unsubscribed\n\n` +
`ā¹ļø **Info:** The contact has been unsubscribed from the list but data is preserved.\n\n` +
`**Full Response:**\n${JSON.stringify(result, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleImportContacts(args: any, api: CakemailAPI) {
try {
const { list_id, contacts, update_existing = false } = args;
if (!list_id || !contacts || !Array.isArray(contacts)) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Fields**\n\nRequired: list_id, contacts (array)'
}]
};
}
if (contacts.length === 0) {
return {
content: [{
type: 'text',
text: 'ā **No Contacts to Import**\n\nThe contacts array is empty.'
}]
};
}
// Import contacts one by one (in a real implementation, this would be a bulk operation)
const results = {
success: 0,
failed: 0,
errors: [] as any[]
};
for (const contact of contacts) {
try {
await api.contacts.createContact({
list_id: parseInt(list_id),
email: contact.email,
first_name: contact.first_name,
last_name: contact.last_name,
custom_fields: contact.custom_fields
});
results.success++;
} catch (error: any) {
results.failed++;
results.errors.push({
email: contact.email,
error: error.message || 'Unknown error'
});
}
}
return {
content: [{
type: 'text',
text: `š„ **Contact Import Complete**\n\n` +
`š **Import Summary:**\n` +
`⢠List ID: ${list_id}\n` +
`⢠Total Contacts: ${contacts.length}\n` +
`⢠Successfully Imported: ${results.success}\n` +
`⢠Failed: ${results.failed}\n` +
`⢠Update Existing: ${update_existing ? 'Yes' : 'No'}\n\n` +
(results.errors.length > 0 ?
`**Errors:**\n${results.errors.map(e =>
`⢠${e.email}: ${e.error}`
).join('\n')}\n\n` : '') +
`**Imported Contacts:**\n${contacts.slice(0, 5).map(c =>
`⢠${c.email} - ${c.first_name || ''} ${c.last_name || ''}`
).join('\n')}` +
(contacts.length > 5 ? `\n⢠... and ${contacts.length - 5} more` : '')
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleTagContacts(args: any, _api: CakemailAPI) {
try {
const { list_id, contact_ids, tags } = args;
if (!list_id || !contact_ids || !tags) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Fields**\n\nRequired: list_id, contact_ids, tags'
}]
};
}
if (!Array.isArray(contact_ids) || !Array.isArray(tags)) {
return {
content: [{
type: 'text',
text: 'ā **Invalid Data Types**\n\ncontact_ids and tags must be arrays'
}]
};
}
// In a real implementation, this would be a bulk operation
// For now, we'll simulate the response
return {
content: [{
type: 'text',
text: `ā
**Contacts Tagged Successfully**\n\n` +
`š·ļø **Tagging Summary:**\n` +
`⢠List ID: ${list_id}\n` +
`⢠Contacts Tagged: ${contact_ids.length}\n` +
`⢠Tags Added: ${tags.join(', ')}\n` +
`⢠Contact IDs: ${contact_ids.slice(0, 10).join(', ')}` +
(contact_ids.length > 10 ? ` ... and ${contact_ids.length - 10} more` : '') +
`\n\nā¹ļø **Info:** Tags have been successfully added to the specified contacts.`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleUntagContacts(args: any, _api: CakemailAPI) {
try {
const { list_id, contact_ids, tags } = args;
if (!list_id || !contact_ids || !tags) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Fields**\n\nRequired: list_id, contact_ids, tags'
}]
};
}
if (!Array.isArray(contact_ids) || !Array.isArray(tags)) {
return {
content: [{
type: 'text',
text: 'ā **Invalid Data Types**\n\ncontact_ids and tags must be arrays'
}]
};
}
// In a real implementation, this would be a bulk operation
// For now, we'll simulate the response
return {
content: [{
type: 'text',
text: `ā
**Tags Removed Successfully**\n\n` +
`š·ļø **Untagging Summary:**\n` +
`⢠List ID: ${list_id}\n` +
`⢠Contacts Updated: ${contact_ids.length}\n` +
`⢠Tags Removed: ${tags.join(', ')}\n` +
`⢠Contact IDs: ${contact_ids.slice(0, 10).join(', ')}` +
(contact_ids.length > 10 ? ` ... and ${contact_ids.length - 10} more` : '') +
`\n\nā¹ļø **Info:** Tags have been successfully removed from the specified contacts.`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}
export async function handleSearchContacts(args: any, api: CakemailAPI) {
try {
const {
list_id, query, filters = {}, page = 1, per_page = 50
} = args;
if (!list_id) {
return {
content: [{
type: 'text',
text: 'ā **Missing Required Field**\n\nRequired: list_id'
}]
};
}
// Build search parameters
const searchParams: any = {
list_id,
page,
per_page
};
// Add filters
if (query) searchParams.email = query;
if (filters.status) searchParams.status = filters.status;
const contacts = await api.contacts.getContacts(searchParams);
const total = contacts.pagination?.count || 0;
const contactData = contacts.data || [];
return {
content: [{
type: 'text',
text: `š **Contact Search Results**\n\n` +
`**Search Parameters:**\n` +
`⢠List ID: ${list_id}\n` +
`⢠Query: ${query || 'none'}\n` +
`⢠Status Filter: ${filters.status || 'all'}\n` +
`⢠Tags Filter: ${filters.tags?.join(', ') || 'none'}\n` +
`⢠Date Range: ${filters.created_after || 'any'} to ${filters.created_before || 'any'}\n\n` +
`**Results (${total} total):**\n\n` +
(contactData.slice(0, 10).map((contact, i) =>
`${i + 1}. **${contact.email}** (${contact.id})\n` +
` š¤ Name: ${contact.first_name || ''} ${contact.last_name || ''}\n` +
` š Status: ${contact.status || 'N/A'}\n` +
` š·ļø Tags: ${(contact as any).tags?.join(', ') || 'None'}\n` +
` š
Created: ${(contact as any).created_on || 'N/A'}`
).join('\n\n') || 'No contacts found matching the search criteria.') +
(total > 10 ? `\n\n**... and ${total - 10} more contacts**` : '') +
`\n\n**Full Response:**\n${JSON.stringify(contacts, null, 2)}`
}]
};
} catch (error) {
return handleCakemailError(error);
}
}