Sunset Timeline
Communicate the deprecation-to-removal timeline so clients can plan migrations with confidence.
Why
A sunset date without a timeline is a threat, not a plan — clients know the cliff is coming but not how to climb down. A timeline breaks the window into phases with specific actions: announce (deprecate + document), remind (escalate warnings + direct contact), restrict (rate-limit or degraded), and remove (410 Gone). Each phase gives laggard clients another chance to migrate, and the progression is visible so nobody can claim surprise. The timeline also forces your team to do the communication work — without phases, "we'll remove it in December" becomes "we removed it in December and emailed nobody." The timeline is the difference between a managed retirement and an outage.
When
For every deprecation. Define the timeline at deprecation time and communicate it in the changelog + headers.
How
1. Announce (month 0): deprecation signal + changelog + docs + email registered users 2. Remind (month 3): escalate to log.warn on every call + direct email to top callers by API key 3. Restrict (month 5): rate-limit the deprecated endpoint (e.g., 10% of normal) to push migration 4. Remove (month 6): return 410 Gone with a Link header to the replacement + migration guide 5. Keep the 410 for one more release cycle so clients get a clear error, not a 404 6. Communicate each phase transition in the changelog and to the deprecation dashboard 7. If usage is still >5% at month 5, extend the timeline rather than breaking clients — communicate the extension
Example
# Sunset timeline for /v1/orders (6-month window)
# Month 0 (2024-06-01): Announce
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2024 GMT');
res.set('Link', '</v2/orders>; rel="successor-version"');
# changelog: "DEPRECATED /v1/orders — use /v2/orders. Removed 2024-12-31."
# Month 3 (2024-09-01): Remind
logger.warn({ key: req.apiKey }, 'v1/orders deprecated — migrate to v2');
# email top 100 callers by usage
# Month 5 (2024-11-01): Restrict
if (req.path.startsWith('/v1/')) {
// rate-limit to 10 req/min (push migration)
await rateLimit(req.apiKey, 'v1-deprecated', 10, 60);
}
# Month 6 (2024-12-01): Remove
app.use('/v1/orders', (req, res) => {
res.status(410).set('Link', '</v2/orders>; rel="successor-version"').json({
error: 'gone', message: 'Use /v2/orders — see docs/migrate-v1-v2.md'
});
});Common Problems
- ⚠Going straight from deprecation to 410 with no intermediate phases — clients have no warning escalation and feel ambushed.
- ⚠Not extending the timeline when usage is still high at the sunset date — you break real users to hit an arbitrary deadline.
- ⚠Removing the 410 immediately and returning 404 — clients can't distinguish "removed" from "never existed" and waste time debugging.
Best Practices
- ✓Use a 4-phase timeline: announce, remind, restrict, remove — each phase gives laggards another chance to migrate.
- ✓Extend the sunset date if usage is still >5% at month 5 — breaking clients to hit a date is a false economy.
- ✓Keep the 410 Gone response (with a Link to the replacement) for one release cycle after removal — it's the clearest possible error.