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
- —Ask at the right time - After positive experiences
- —Keep it simple - One question is better than ten
- —Make it easy - One-click responses in email
- —Close the loop - Tell users how you used their feedback
- —Don't over-ask - Limit feedback requests per user
- —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.