emailr_
All articles
usecase·8 min

Feedback request emails: When and how to ask

feedbacksurveysengagement

Feedback emails help you understand users and improve your product. But timing and approach matter—poorly timed requests annoy users and yield low response rates. Here's how to ask effectively.

Feedback timing

Trigger-based requests

const feedbackTriggers = [
  {
    event: 'feature_used_10_times',
    delay: '1 day',
    template: 'feature-feedback',
    question: 'How useful is this feature?'
  },
  {
    event: 'subscription_month_3',
    delay: '0',
    template: 'nps-survey',
    question: 'How likely are you to recommend us?'
  },
  {
    event: 'support_ticket_resolved',
    delay: '1 hour',
    template: 'support-feedback',
    question: 'How was your support experience?'
  },
  {
    event: 'onboarding_complete',
    delay: '3 days',
    template: 'onboarding-feedback',
    question: 'How was getting started?'
  }
];

NPS survey email

await sendEmail({
  to: user.email,
  subject: 'Quick question about your experience',
  template: 'nps-survey',
  data: {
    user,
    question: 'How likely are you to recommend us to a colleague?',
    // One-click rating in email
    ratingUrls: Array.from({ length: 11 }, (_, i) => ({
      score: i,
      url: `${baseUrl}/feedback/nps?score=${i}&token=${token}`
    }))
  }
});

Feature feedback

In-context feedback

await sendEmail({
  to: user.email,
  subject: `How's ${feature.name} working for you?`,
  template: 'feature-feedback',
  data: {
    user,
    feature: {
      name: feature.name,
      usageCount: usage.count,
      firstUsed: usage.firstUsedAt
    },
    question: 'How useful is this feature for your workflow?',
    options: [
      { label: 'Very useful', value: 5, url: feedbackUrl(5) },
      { label: 'Somewhat useful', value: 3, url: feedbackUrl(3) },
      { label: 'Not useful', value: 1, url: feedbackUrl(1) }
    ]
  }
});

Beta feedback

await sendEmail({
  to: user.email,
  subject: 'Your feedback on the beta',
  template: 'beta-feedback',
  data: {
    user,
    betaFeature: feature.name,
    questions: [
      'What worked well?',
      'What was confusing?',
      'What\'s missing?'
    ],
    feedbackFormUrl: `${baseUrl}/beta/${feature.slug}/feedback`,
    incentive: 'Get 3 months free when we launch'
  }
});

Support follow-up

Post-resolution feedback

await sendEmail({
  to: user.email,
  subject: 'How was your support experience?',
  template: 'support-feedback',
  data: {
    user,
    ticket: {
      id: ticket.id,
      subject: ticket.subject,
      resolvedAt: ticket.resolvedAt,
      agent: ticket.assignedAgent.name
    },
    rating: {
      question: 'How would you rate your support experience?',
      options: [
        { emoji: '😊', label: 'Great', value: 5 },
        { emoji: '😐', label: 'Okay', value: 3 },
        { emoji: '😞', label: 'Poor', value: 1 }
      ]
    },
    followUpUrl: `${baseUrl}/support/tickets/${ticket.id}`
  }
});

Onboarding feedback

Post-onboarding survey

await sendEmail({
  to: user.email,
  subject: 'Quick question about getting started',
  template: 'onboarding-feedback',
  data: {
    user,
    completedSteps: onboarding.completedSteps,
    timeToComplete: onboarding.duration,
    questions: [
      {
        id: 'ease',
        text: 'How easy was it to get started?',
        type: 'scale',
        min: 1,
        max: 5
      },
      {
        id: 'missing',
        text: 'Was anything confusing or missing?',
        type: 'text'
      }
    ],
    surveyUrl: `${baseUrl}/feedback/onboarding?token=${token}`
  }
});

Churn feedback

Cancellation survey

await sendEmail({
  to: user.email,
  subject: 'One quick question before you go',
  template: 'churn-feedback',
  data: {
    user,
    subscription: {
      plan: subscription.plan,
      duration: subscription.tenure
    },
    question: 'What\'s the main reason you\'re leaving?',
    options: [
      { label: 'Too expensive', value: 'price' },
      { label: 'Missing features', value: 'features' },
      { label: 'Switched to competitor', value: 'competitor' },
      { label: 'No longer needed', value: 'not_needed' },
      { label: 'Other', value: 'other' }
    ],
    feedbackUrl: `${baseUrl}/feedback/cancellation?token=${token}`,
    winBackOffer: 'Share feedback and get 50% off if you return'
  }
});

Best practices for feedback emails

Keep it short

// Good: One question, one click
const simpleNPS = {
  subject: 'One quick question',
  body: 'How likely are you to recommend us?',
  action: '10 clickable numbers in email'
};

// Bad: Long survey link
const longSurvey = {
  subject: 'Please complete our 15-question survey',
  body: '...',
  action: 'Link to external survey tool'
};

Timing matters

const feedbackTiming = {
  // Good times
  afterPositiveExperience: true,  // Just completed a task
  afterMilestone: true,           // 30 days, 100 uses, etc.
  afterSupportResolution: true,   // 1 hour after close
  
  // Bad times
  duringOnboarding: false,        // Let them learn first
  afterNegativeExperience: false, // They're already frustrated
  tooFrequently: false            // Max once per month
};

Incentivize thoughtfully

const incentiveStrategy = {
  // Low-effort feedback (NPS, ratings)
  lowEffort: {
    incentive: 'none',
    reason: 'Quick enough that incentive isn\'t needed'
  },
  
  // Medium-effort (short survey)
  mediumEffort: {
    incentive: 'entry into drawing',
    reason: 'Small reward for small effort'
  },
  
  // High-effort (interview, detailed feedback)
  highEffort: {
    incentive: 'gift card or account credit',
    reason: 'Compensate for significant time'
  }
};

Analyzing feedback

Response tracking

interface FeedbackMetrics {
  responseRate: number;
  averageScore: number;
  scoreDistribution: Record<number, number>;
  commonThemes: string[];
  actionableInsights: string[];
}

async function analyzeFeedback(campaignId: string): Promise<FeedbackMetrics> {
  const responses = await getFeedbackResponses(campaignId);
  
  return {
    responseRate: responses.length / emailsSent,
    averageScore: calculateAverage(responses.map(r => r.score)),
    scoreDistribution: groupBy(responses, 'score'),
    commonThemes: extractThemes(responses.map(r => r.text)),
    actionableInsights: identifyActionItems(responses)
  };
}

Best practices

  1. Ask at the right time - After positive experiences
  2. Keep it simple - One question is better than ten
  3. Make it easy - One-click responses in email
  4. Close the loop - Tell users how you used their feedback
  5. Don't over-ask - Limit feedback requests per user
  6. Segment requests - Different questions for different users

Good feedback emails feel like a conversation, not an interrogation. Make it easy for users to share their thoughts.

e_

Written by the emailr team

Building email infrastructure for developers

Ready to start sending?

Get your API key and send your first email in under 5 minutes. No credit card required.