Skip to main content

Command Palette

Search for a command to run...

Elasticsearch Full-Text Search: Production Setup Guide

Published
6 min read
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Elasticsearch Full-Text Search: Production Setup Guide

The Search That Cost Us 10,000 Users (And How We Fixed It)

Our search was broken. Users couldn't find anything. Here's how we rebuilt it in 48 hours.

Table of Contents

  • Search & Email 2026
  • Architecture Fundamentals
  • 5 Implementation Patterns
  • Performance Optimization
  • Relevance Tuning
  • Analytics Integration
  • Scaling Strategy
  • FAQ
  • Production Checklist

Search & Email in 2026

Discovery and communication are critical.

User Expectations

// Modern search requirements
interface SearchRequirements {
  speed: '<50ms response time';
  typoTolerance: 'Fix misspellings automatically';
  relevance: 'Show best results first';
  filtering: 'Multi-faceted filtering';
  highlighting: 'Show matching text';
}

Email Deliverability

// Email success metrics
const emailMetrics = {
  deliveryRate: 0.98,    // 98% delivered
  openRate: 0.25,        // 25% opened
  clickRate: 0.05,       // 5% clicked
  bounceRate: 0.02,      // 2% bounced
  spamRate: 0.001        // 0.1% marked spam
};

Business Impact

Good search = happy users = more conversions.

Architecture Fundamentals

Building scalable search and email.

Search Index

// Index documents
interface Document {
  id: string;
  title: string;
  content: string;
  category: string;
  tags: string[];
  createdAt: number;
  metadata: Record<string, any>;
}

// Create index
const index = await searchClient.createIndex('products', {
  primaryKey: 'id',
  searchableAttributes: ['title', 'content'],
  filterableAttributes: ['category', 'tags'],
  sortableAttributes: ['createdAt', 'price']
});

// Add documents
await index.addDocuments([
  {
    id: '1',
    title: 'Product Name',
    content: 'Description...',
    category: 'electronics',
    tags: ['new', 'sale']
  }
]);

Email Templates

// React Email component
import { Html, Head, Body, Container, Text } from '@react-email/components';

export function WelcomeEmail({ name }: { name: string }) {
  return (
    <Html>
      <Head />
      <Body style={{ backgroundColor: '#f6f9fc' }}>
        <Container>
          <Text style={{ fontSize: 24, fontWeight: 'bold' }}>
            Welcome, {name}!
          </Text>
          <Text>
            Thanks for signing up. Get started with these resources.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Frontend Implementation

// React search component
import { useSearch } from './hooks';

function SearchBox() {
  const { query, setQuery, results, isLoading } = useSearch();

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />

      {isLoading && <Spinner />}

      <div>
        {results.map(result => (
          <SearchResult key={result.id} data={result} />
        ))}
      </div>
    </div>
  );
}

// Custom hook
function useSearch() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }

    setIsLoading(true);

    const timeoutId = setTimeout(async () => {
      const data = await searchAPI.search(query);
      setResults(data.hits);
      setIsLoading(false);
    }, 300); // Debounce

    return () => clearTimeout(timeoutId);
  }, [query]);

  return { query, setQuery, results, isLoading };
}

Highlighting

// Highlight matching terms
function highlightMatches(text: string, query: string): string {
  const regex = new RegExp(`(${query})`, 'gi');
  return text.replace(regex, '<mark>$1</mark>');
}

Filters

// Multi-faceted filtering
interface SearchFilters {
  category?: string[];
  tags?: string[];
  priceRange?: { min: number; max: number };
  dateRange?: { start: Date; end: Date };
}

async function search(
  query: string,
  filters: SearchFilters
) {
  const results = await searchClient.search(query, {
    filter: [
      filters.category?.map(c => `category = ${c}`).join(' OR '),
      filters.tags?.map(t => `tags = ${t}`).join(' OR '),
      filters.priceRange && 
        `price >= ${filters.priceRange.min} AND price <= ${filters.priceRange.max}`
    ].filter(Boolean).join(' AND ')
  });

  return results;
}

Aggregations

// Get filter counts
const facets = await searchClient.getFacets('products', {
  facets: ['category', 'tags'],
  query: 'laptop'
});

// Result:
// {
//   category: { 'electronics': 123, 'computers': 89 },
//   tags: { 'sale': 45, 'new': 67 }
// }

Pattern 3: Email Sending

Transactional Emails

// Send transactional email
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendWelcomeEmail(user: User) {
  const { data, error } = await resend.emails.send({
    from: 'noreply@example.com',
    to: user.email,
    subject: 'Welcome to Our Platform!',
    react: WelcomeEmail({ name: user.name })
  });

  if (error) {
    logger.error('Email send failed', { error, userId: user.id });
    throw error;
  }

  // Track sent email
  await db.emails.create({
    userId: user.id,
    type: 'welcome',
    provider: 'resend',
    messageId: data.id,
    sentAt: new Date()
  });
}

Batch Sending

// Send to multiple recipients
async function sendBulkEmails(users: User[], template: string) {
  const BATCH_SIZE = 100;

  for (let i = 0; i < users.length; i += BATCH_SIZE) {
    const batch = users.slice(i, i + BATCH_SIZE);

    await Promise.all(
      batch.map(user =>
        resend.emails.send({
          from: 'newsletter@example.com',
          to: user.email,
          subject: getSubject(template, user),
          react: getTemplate(template, user)
        })
      )
    );

    // Rate limit between batches
    await delay(1000);
  }
}

Pattern 4: Personalization

Search Ranking

// Personalized results
async function personalizedSearch(
  query: string,
  userId: string
) {
  // Get user preferences
  const prefs = await getUserPreferences(userId);

  // Boost relevant categories
  const results = await searchClient.search(query, {
    rankingRules: [
      'typo',
      'words',
      'proximity',
      'attribute',
      'sort',
      'exactness',
      // Custom: boost user's interests
      `category:${prefs.favoriteCategories.join(',')}`
    ]
  });

  return results;
}

Email Personalization

// Dynamic content
function PersonalizedEmail({ user }: { user: User }) {
  return (
    <Html>
      <Body>
        <Text>Hi {user.name},</Text>

        {user.lastPurchase && (
          <Text>
            Based on your purchase of {user.lastPurchase.product}, 
            you might like these recommendations:
          </Text>
        )}

        <ProductList products={getRecommendations(user)} />
      </Body>
    </Html>
  );
}

Pattern 5: Analytics

Search Analytics

// Track search behavior
async function trackSearch(query: string, userId?: string) {
  await analytics.track('Search Performed', {
    query,
    userId,
    resultsCount: results.length,
    timestamp: Date.now()
  });

  // Track no-results searches
  if (results.length === 0) {
    await analytics.track('Search No Results', {
      query,
      userId
    });
  }
}

Email Analytics

// Track email events
const webhookHandler = async (event: EmailEvent) => {
  switch (event.type) {
    case 'delivered':
      await trackEmailDelivered(event);
      break;
    case 'opened':
      await trackEmailOpened(event);
      break;
    case 'clicked':
      await trackEmailClicked(event);
      break;
    case 'bounced':
      await handleBounce(event);
      break;
    case 'complained':
      await handleComplaint(event);
      break;
  }
};

Performance Benchmarks

OperationLatencyThroughput
Search Query10-50ms1000 req/s
Index Update100ms100 docs/s
Email Send200ms50 emails/s
Email Render50ms200/s

Scaling Strategy

Search Sharding

// Distribute across shards
const shardId = hashUserId(userId) % NUM_SHARDS;
await searchClients[shardId].search(query);

Email Queues

// Queue emails for reliability
import { Queue } from 'bullmq';

const emailQueue = new Queue('emails', {
  connection: redis
});

// Add to queue
await emailQueue.add('send', {
  to: user.email,
  template: 'welcome',
  data: { name: user.name }
}, {
  attempts: 3,
  backoff: {
    type: 'exponential',
    delay: 1000
  }
});

FAQ

Managed for simplicity, self-hosted for control.

Q2: Email deliverability tips?

Warm up IPs, authenticate (SPF/DKIM/DMARC), monitor reputation.

Q3: Handle search typos?

Use fuzzy matching and phonetic algorithms.

Q4: Email design best practices?

Mobile-first, plain text alternative, test across clients.

Q5: Cost at scale?

Search: $100-500/month, Email: $0.10-0.50 per 1000 emails.

Production Checklist

  • [ ] Index schema designed
  • [ ] Relevance tuned
  • [ ] Filters implemented
  • [ ] Analytics tracking
  • [ ] Monitoring setup

Email

  • [ ] DNS records configured
  • [ ] Templates designed
  • [ ] Unsubscribe flow
  • [ ] Bounce handling
  • [ ] Spam monitoring

Conclusion

Search and email are infrastructure.

Key takeaways:

  • Instant search is expected
  • Email deliverability matters
  • Track everything
  • Personalize when possible
  • Monitor continuously

Build discovery and communication that works.

Resources:

  • Search Best Practices
  • Email Deliverability Guide
  • Template Libraries
  • Analytics Setup

Next Steps:

  1. Set up search index
  2. Design email templates
  3. Configure DNS
  4. Implement tracking
  5. Monitor metrics

Improve discovery today.

Elasticsearch Full-Text Search: Production Setup Guide