← Back to tech insights

February 19, 2026 · 9 min

AI-Powered Classified Ads: How AI Improves User Engagement

How I used AI to transform a traditional classified ads platform — from smart listing generation to personalized recommendations and fraud detection.

AI-Powered Classified Ads: How AI Improves User Engagement

TL;DR: Traditional classified ad platforms treat every user the same and rely on users to write their own listings. AI changes both of these fundamentally. Here's how I used AI to increase engagement and listing quality on a classified ads platform.


The Classified Ads Problem

Traditional platforms like Craigslist have barely changed in 25 years. The core user experience problems:

  1. Low listing quality — sellers write vague, inconsistent listings
  2. Poor discoverability — search is keyword-only, no semantic understanding
  3. No personalization — same results for every user
  4. Fraud and spam — easy to post fake listings
  5. Stale listings — outdated posts clutter search results

AI provides practical solutions to all five.


Feature 1: AI-Assisted Listing Creation

Most sellers don't know how to write a compelling listing. The AI assistant guides them:

// features/listings/actions/generate-listing.ts
'use server';

import { openai } from '@/lib/openai';

interface ListingGenerationInput {
  category: string;
  userDescription: string;  // What the user typed, usually a few sentences
  uploadedImages?: string[]; // Image URLs for vision analysis
  priceHint?: string;
}

export async function generateListingContent(
  input: ListingGenerationInput
): Promise<GeneratedListing> {
  
  const systemPrompt = `You are an expert at writing compelling classified ad listings. 
  Generate professional, honest, and detailed listings that attract serious buyers.
  
  Guidelines:
  - Title: Clear, searchable, 8-12 words
  - Description: Highlight key features, condition, usage history
  - Include relevant details for the ${input.category} category
  - Suggest fair price range based on market knowledge
  - Generate relevant tags for discoverability`;
  
  const userPrompt = `Create a listing for: "${input.userDescription}"
  Category: ${input.category}
  Price hint they mentioned: ${input.priceHint || 'not specified'}
  
  Return valid JSON with: title, description, suggestedPrice, tags[], condition`;
  
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: userPrompt },
    ],
    response_format: { type: 'json_object' },
  });
  
  const generated = JSON.parse(response.choices[0].message.content!);
  
  // Validate generated content
  return GeneratedListingSchema.parse(generated);
}

Results: Listings generated with AI assistance get 2.3× more views and 1.8× more contact requests than manually written listings of similar items.


Feature 2: Image-to-Listing (Multimodal AI)

Users can now just upload photos and the AI writes the listing:

export async function generateFromImages(
  imageUrls: string[],
  category: string
): Promise<GeneratedListing> {
  
  // Build vision prompt with image URLs
  const imageMessages = imageUrls.map(url => ({
    type: 'image_url' as const,
    image_url: { url, detail: 'high' as const }
  }));
  
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{
      role: 'user',
      content: [
        ...imageMessages,
        {
          type: 'text',
          text: `Analyze these photos and create a classified ad listing for the ${category} category. 
          Identify: item name, condition (new/excellent/good/fair/poor), visible features, 
          any visible wear or damage, estimated age if apparent.
          Return JSON: { title, description, condition, estimatedAge, tags[], concerns[] }`
        }
      ]
    }],
    response_format: { type: 'json_object' },
  });
  
  return JSON.parse(response.choices[0].message.content!);
}

Feature 3: Semantic Search

Keyword search misses the intent behind queries. Semantic search understands it:

User searches: "transport my dog on weekends"
Keyword search returns: listings containing "dog", "transport", "weekends" literally
Semantic search returns: pet carriers, dog crates, car seat covers, dog seat belts, kennels

Implementation using vector embeddings:

// When a listing is created, embed it
async function embedListing(listing: Listing): Promise<void> {
  const textToEmbed = `
    ${listing.title}
    ${listing.description}
    Category: ${listing.category}
    Tags: ${listing.tags.join(', ')}
    Condition: ${listing.condition}
  `;
  
  const embedding = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: textToEmbed,
  });
  
  // Store embedding in PostgreSQL with pgvector extension
  await prisma.$executeRaw`
    UPDATE listings 
    SET embedding = ${embedding.data[0].embedding}::vector
    WHERE id = ${listing.id}
  `;
}

// Semantic search query
async function semanticSearch(
  query: string,
  filters: SearchFilters,
  limit = 20
): Promise<Listing[]> {
  
  // Embed the search query
  const queryEmbedding = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: query,
  });
  
  const vector = queryEmbedding.data[0].embedding;
  
  // Find semantically similar listings using cosine similarity
  const results = await prisma.$queryRaw<Listing[]>`
    SELECT *, 1 - (embedding <=> ${vector}::vector) AS similarity
    FROM listings
    WHERE 
      category = ${filters.category}
      AND price BETWEEN ${filters.minPrice} AND ${filters.maxPrice}
      AND deleted_at IS NULL
      AND expires_at > NOW()
    ORDER BY embedding <=> ${vector}::vector
    LIMIT ${limit}
  `;
  
  return results.filter(r => r.similarity > 0.5); // Minimum relevance threshold
}

Feature 4: Personalized Recommendations

The recommendation engine analyzes user behavior to surface relevant listings:

class RecommendationEngine {
  async getRecommendations(userId: string): Promise<Listing[]> {
    // Get user's interaction history
    const interactions = await prisma.userInteraction.findMany({
      where: { userId },
      orderBy: { timestamp: 'desc' },
      take: 50,
      include: { listing: true },
    });
    
    if (interactions.length < 3) {
      // Cold start: return popular listings in user's location
      return this.getPopularListings(userId);
    }
    
    // Build user preference vector from viewed/saved listings
    const preferenceVector = await this.buildPreferenceVector(
      interactions.map(i => i.listing)
    );
    
    // Find listings similar to user's preference vector
    return prisma.$queryRaw`
      SELECT l.*, 
             1 - (l.embedding <=> ${preferenceVector}::vector) AS relevance_score
      FROM listings l
      WHERE 
        l.user_id != ${userId}
        AND l.id NOT IN (SELECT listing_id FROM user_interactions WHERE user_id = ${userId})
        AND l.expires_at > NOW()
      ORDER BY l.embedding <=> ${preferenceVector}::vector
      LIMIT 20
    `;
  }
  
  private async buildPreferenceVector(viewedListings: Listing[]): Promise<number[]> {
    // Weight recent interactions more heavily
    const weightedEmbeddings = await Promise.all(
      viewedListings.map(async (listing, idx) => {
        const weight = 1 / (idx + 1); // More recent = higher weight
        const embedding = await this.getListingEmbedding(listing.id);
        return embedding.map(v => v * weight);
      })
    );
    
    // Average the weighted embeddings
    const sumVector = weightedEmbeddings.reduce(
      (acc, emb) => acc.map((v, i) => v + emb[i]),
      new Array(1536).fill(0)
    );
    
    const totalWeight = viewedListings.reduce((acc, _, idx) => acc + 1 / (idx + 1), 0);
    return sumVector.map(v => v / totalWeight);
  }
}

Feature 5: AI Fraud Detection

Detecting fake or scam listings is critical for platform trust:

async function scoreListing(listing: ListingInput): Promise<FraudScore> {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{
      role: 'user',
      content: `Analyze this classified ad listing for potential fraud indicators.
      
      Title: ${listing.title}
      Description: ${listing.description}
      Price: $${listing.price} (Category avg: $${listing.categoryAvgPrice})
      Contact: ${listing.contactMethod}
      
      Return JSON with:
      - fraudScore: 0-100 (100 = definitely fraudulent)
      - flags: string[] (specific concerns found)
      - recommendation: "approve" | "review" | "reject"
      
      Common fraud signals: 
      - Price far below market value
      - Urgency pressure ("must sell today")
      - Asking for wire transfer/gift cards
      - Too-good-to-be-true descriptions
      - Generic stock photo language
      - Requests to communicate off-platform`
    }],
    response_format: { type: 'json_object' },
  });
  
  const result = JSON.parse(response.choices[0].message.content!);
  
  // Auto-reject high-confidence fraud
  if (result.fraudScore > 85) {
    await flagListingForRemoval(listing.id, result.flags);
  }
  
  // Queue for human review if uncertain
  if (result.fraudScore > 50) {
    await addToModerationQueue(listing.id, result);
  }
  
  return result;
}

Feature 6: Smart Pricing Suggestions

async function suggestPrice(
  listing: ListingInput,
  category: string
): Promise<PriceSuggestion> {
  
  // Get recently sold similar items for reference
  const comparables = await semanticSearch(
    `${listing.title} ${listing.condition}`,
    { category, status: 'sold' },
    10
  );
  
  const priceRange = comparables.length > 0 
    ? {
        min: Math.min(...comparables.map(l => l.price)),
        max: Math.max(...comparables.map(l => l.price)),
        avg: comparables.reduce((s, l) => s + l.price, 0) / comparables.length,
      }
    : null;
  
  const suggestion = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{
      role: 'user',
      content: `Suggest a price for this classified listing:
      
      Item: ${listing.title}
      Condition: ${listing.condition}
      Description: ${listing.description}
      Recently sold comparable range: ${JSON.stringify(priceRange)}
      
      Return JSON: { suggestedPrice, minPrice, maxPrice, reasoning }`
    }],
    response_format: { type: 'json_object' },
  });
  
  return JSON.parse(suggestion.choices[0].message.content!);
}

Engagement Impact

After launching AI features:

| Metric | Before AI | After AI | Change | |--------|-----------|----------|--------| | Time to create listing | 8 min avg | 2.5 min avg | -69% | | Listing completion rate | 52% | 84% | +62% | | Search result relevance (satisfaction) | 58% | 82% | +41% | | Fraud reports per 1000 listings | 12 | 3 | -75% | | Return visits per user | 2.1/week | 3.7/week | +76% |


Explore AI Classified Ads: GitHub | Portfolio