emailr_
All articles
usecase·9 min

Subscription renewal emails: Timing and content

billingrenewalsretention

Subscription renewal emails directly impact retention and revenue. Well-timed, clear communications reduce churn and failed payments. Here's how to design an effective renewal email flow.

Renewal email sequence

Timeline

const renewalSequence = [
  { daysBefore: 14, template: 'renewal-reminder-14d' },
  { daysBefore: 7, template: 'renewal-reminder-7d' },
  { daysBefore: 3, template: 'renewal-reminder-3d' },
  { daysBefore: 0, template: 'renewal-processed' },
  { daysAfter: 1, template: 'renewal-failed', condition: 'payment_failed' }
];

Reminder email

interface RenewalReminder {
  user: User;
  subscription: {
    plan: string;
    amount: number;
    currency: string;
    renewsAt: Date;
    paymentMethod: string;
  };
}

await sendEmail({
  to: user.email,
  subject: `Your ${subscription.plan} plan renews in ${daysUntil} days`,
  template: 'renewal-reminder',
  data: {
    user,
    subscription,
    updatePaymentUrl: `${baseUrl}/billing/payment-method`,
    changePlanUrl: `${baseUrl}/billing/plans`,
    cancelUrl: `${baseUrl}/billing/cancel`
  }
});

Renewal confirmation

await sendEmail({
  to: user.email,
  subject: `Your ${subscription.plan} plan has renewed`,
  template: 'renewal-confirmation',
  data: {
    user,
    subscription,
    invoice: {
      id: invoice.id,
      amount: invoice.amount,
      pdfUrl: invoice.pdfUrl
    },
    nextRenewal: subscription.nextRenewalDate,
    usageSummary: await getUsageSummary(user.id)
  }
});

Failed payment recovery

Dunning sequence

const dunningSequence = [
  {
    attempt: 1,
    delay: 0,
    template: 'payment-failed-1',
    subject: 'Payment failed - please update your card'
  },
  {
    attempt: 2,
    delay: 3, // days
    template: 'payment-failed-2',
    subject: 'Second attempt failed - action required'
  },
  {
    attempt: 3,
    delay: 7,
    template: 'payment-failed-3',
    subject: 'Final notice - your account will be suspended'
  },
  {
    attempt: 4,
    delay: 10,
    template: 'account-suspended',
    subject: 'Your account has been suspended'
  }
];

Payment failed email

await sendEmail({
  to: user.email,
  subject: 'Payment failed - please update your card',
  template: 'payment-failed',
  data: {
    user,
    subscription,
    failureReason: getReadableFailureReason(payment.failureCode),
    retryDate: payment.nextRetryAt,
    updateCardUrl: `${baseUrl}/billing/payment-method`,
    gracePeriodEnds: subscription.gracePeriodEnds
  },
  priority: 'high'
});

function getReadableFailureReason(code: string): string {
  const reasons = {
    'card_declined': 'Your card was declined',
    'insufficient_funds': 'Insufficient funds',
    'expired_card': 'Your card has expired',
    'incorrect_cvc': 'Incorrect security code',
    'processing_error': 'Processing error - we\'ll retry'
  };
  return reasons[code] || 'Payment could not be processed';
}

Plan change notifications

Upgrade confirmation

await sendEmail({
  to: user.email,
  subject: `Welcome to ${newPlan.name}!`,
  template: 'plan-upgraded',
  data: {
    user,
    previousPlan: oldPlan.name,
    newPlan: {
      name: newPlan.name,
      features: getNewFeatures(oldPlan, newPlan),
      price: newPlan.price
    },
    prorated: {
      credit: proratedCredit,
      charged: proratedCharge
    },
    gettingStartedUrl: `${baseUrl}/getting-started/${newPlan.slug}`
  }
});

Downgrade confirmation

await sendEmail({
  to: user.email,
  subject: `Your plan change to ${newPlan.name}`,
  template: 'plan-downgraded',
  data: {
    user,
    currentPlan: oldPlan.name,
    newPlan: newPlan.name,
    effectiveDate: downgrade.effectiveDate,
    lostFeatures: getLostFeatures(oldPlan, newPlan),
    dataRetention: 'Your data will be preserved',
    upgradeUrl: `${baseUrl}/billing/plans`
  }
});

Cancellation flow

Cancellation confirmation

await sendEmail({
  to: user.email,
  subject: 'Your subscription has been cancelled',
  template: 'subscription-cancelled',
  data: {
    user,
    subscription: {
      plan: subscription.plan,
      endsAt: subscription.currentPeriodEnd,
      // Access continues until period end
      accessUntil: subscription.currentPeriodEnd
    },
    dataExport: {
      available: true,
      exportUrl: `${baseUrl}/settings/export`
    },
    feedback: {
      url: `${baseUrl}/feedback/cancellation`,
      incentive: 'Help us improve and get 1 month free if you return'
    },
    reactivateUrl: `${baseUrl}/billing/reactivate`
  }
});

Win-back after cancellation

// Send 7 days before access ends
await sendEmail({
  to: user.email,
  subject: 'Your access ends in 7 days',
  template: 'access-ending-soon',
  data: {
    user,
    endsAt: subscription.currentPeriodEnd,
    whatYouLose: [
      `${usage.projectCount} projects`,
      `${usage.teamMembers} team members`,
      'All integrations'
    ],
    specialOffer: {
      discount: '30% off for 3 months',
      code: 'COMEBACK30',
      expiresAt: subscription.currentPeriodEnd
    },
    reactivateUrl: `${baseUrl}/billing/reactivate?offer=COMEBACK30`
  }
});

Annual renewal handling

Annual renewal reminder

// 30 days before annual renewal
await sendEmail({
  to: user.email,
  subject: 'Your annual subscription renews soon',
  template: 'annual-renewal-reminder',
  data: {
    user,
    subscription: {
      plan: subscription.plan,
      amount: subscription.annualPrice,
      renewsAt: subscription.renewsAt,
      savings: calculateAnnualSavings(subscription)
    },
    yearInReview: {
      // Show value received
      emailsSent: usage.totalEmails,
      uptime: '99.99%',
      supportTickets: usage.supportTickets
    },
    options: {
      continueAnnual: 'Continue annual billing',
      switchMonthly: 'Switch to monthly',
      cancel: 'Cancel subscription'
    },
    manageUrl: `${baseUrl}/billing`
  }
});

Best practices

  1. Remind early - Give users time to update payment methods
  2. Be specific - Include exact amounts and dates
  3. Explain failures - Tell users why payment failed
  4. Provide easy fixes - One-click to update payment
  5. Show value - Remind users what they're paying for
  6. Grace periods - Don't cut access immediately
  7. Multiple channels - Consider SMS for failed payments

Renewal emails are about reducing friction and preventing involuntary churn. Make it easy for users to stay subscribed.

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.