DEV Community

Olivier Moussalli
Olivier Moussalli

Posted on

Maintenance Plans & Grace Periods in C#: Automating Software Updates and Renewals

Your customer bought your software 18 months ago for $1,000. Their maintenance plan expired 6 months ago. They call support with a bug. You fixed it in version 2.1, but they're still on 1.8. Now what?

Without a clear maintenance plan strategy, you're stuck: give them free updates forever (losing revenue), or tell them to buy an upgrade (losing customers). This article shows you how to implement maintenance plans that keep customers happy while maximizing renewal revenue.

The Maintenance Plan Model

What is a maintenance plan?

A maintenance plan (sometimes called "Software Assurance" or "Support & Updates") is an optional yearly subscription that grants:

  • Free software updates
  • Technical support
  • Bug fixes
  • Minor version upgrades

Perpetual License + Maintenance Plan = Hybrid Model

Purchase: $1,000 (one-time) → Lifetime license
Maintenance: $200/year (optional) → Updates + support

Timeline:
Year 0: $1,000 (license) + $200 (first year maintenance) = $1,200
Year 1: $200 (renewal)
Year 2: $200 (renewal)
Year 3: $200 (renewal)
Enter fullscreen mode Exit fullscreen mode

Why maintenance plans work:

  • Customers own the software (perpetual license)
  • Vendors get recurring revenue (maintenance renewals)
  • Customers choose when to pay (not forced subscriptions)
  • Updates are tied to active maintenance

More info: QLM Maintenance Plans

Maintenance Plans vs Subscriptions

Key Differences

Maintenance Plan (Optional):

✅ Customer owns license forever
✅ Software works even if maintenance expires
⚠️ No updates without active maintenance
⚠️ Technical support may be limited
Enter fullscreen mode Exit fullscreen mode

Subscription (Required):

⚠️ Customer doesn't own license
❌ Software stops working if subscription expires
✅ Always on latest version
✅ Included support
Enter fullscreen mode Exit fullscreen mode

Revenue Comparison

Perpetual + Maintenance:

Year 0: $1,000 + $200 = $1,200
Year 1: $200 (60% renew) = $120 average
Year 2: $200 (60% renew) = $120 average
Year 3: $200 (60% renew) = $120 average

5-year LTV: $1,560
Enter fullscreen mode Exit fullscreen mode

Subscription Only:

Year 0: $400
Year 1: $400 (70% retain) = $280 average
Year 2: $400 (70% retain) = $196 average
Year 3: $400 (70% retain) = $137 average

5-year LTV: $1,013
Enter fullscreen mode Exit fullscreen mode

Key insight: Perpetual + Maintenance generates 54% more revenue over 5 years with better retention (customers already paid upfront).

Learn more: Perpetual vs Subscription

Implementing Maintenance Plans

Step 1: Create License with Maintenance Plan

using QLM.LicenseLib;

public class MaintenancePlanManager
{
    private LicenseValidator lv;

    public MaintenancePlanManager(string settingsFile)
    {
        lv = new LicenseValidator(settingsFile);
    }

    public string CreateLicenseWithMaintenance(
        string customerEmail,
        int maintenanceYears = 1)
    {
        string activationKey;
        string response;

        // Calculate maintenance expiry
        DateTime maintenanceExpiry = DateTime.Now.AddYears(maintenanceYears);

        bool success = lv.QlmLicenseObject.CreateActivationKey(
            webServiceUrl: lv.QlmLicenseObject.DefaultWebServiceUrl,
            email: customerEmail,
            features: 1,
            productID: 1,
            majorVersion: 1,
            minorVersion: 0,
            licenseModel: ELicenseModel.permanent, // Perpetual license
            numberOfLicenses: 1,
            expiryDate: DateTime.MaxValue, // License never expires
            maintenanceExpiryDate: maintenanceExpiry, // Maintenance DOES expire
            activationKey: out activationKey,
            response: out response
        );

        if (success)
        {
            SendLicenseEmail(customerEmail, activationKey, maintenanceExpiry);
            return activationKey;
        }

        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Check Maintenance Plan Status

public class MaintenanceChecker
{
    public MaintenanceStatus CheckMaintenanceStatus()
    {
        var lv = new LicenseValidator("settings.xml");

        // Get maintenance plan expiry date
        DateTime maintenanceExpiry = lv.QlmLicenseObject.MaintenancePlanExpiryDate;

        // Check if maintenance is active
        bool isActive = DateTime.Now <= maintenanceExpiry;

        // Calculate days until expiry
        int daysRemaining = (maintenanceExpiry - DateTime.Now).Days;

        return new MaintenanceStatus
        {
            IsActive = isActive,
            ExpiryDate = maintenanceExpiry,
            DaysRemaining = Math.Max(0, daysRemaining),
            CanUpdate = isActive
        };
    }

    public bool IsMaintenanceActive()
    {
        var status = CheckMaintenanceStatus();
        return status.IsActive;
    }
}

public class MaintenanceStatus
{
    public bool IsActive { get; set; }
    public DateTime ExpiryDate { get; set; }
    public int DaysRemaining { get; set; }
    public bool CanUpdate { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Enforce Maintenance for Updates

public class UpdateChecker
{
    public void CheckForUpdates()
    {
        var maintenanceChecker = new MaintenanceChecker();
        var status = maintenanceChecker.CheckMaintenanceStatus();

        if (!status.IsActive)
        {
            ShowMaintenanceExpiredDialog(status.ExpiryDate);
            return;
        }

        // Maintenance active - check for updates
        CheckForAvailableUpdates();
    }

    private void ShowMaintenanceExpiredDialog(DateTime expiryDate)
    {
        DialogResult result = MessageBox.Show(
            $"Your maintenance plan expired on {expiryDate:yyyy-MM-dd}.\n\n" +
            "Renew your maintenance plan to receive:\n" +
            "• Latest software updates\n" +
            "• New features\n" +
            "• Bug fixes\n" +
            "• Technical support\n\n" +
            "Would you like to renew now?",
            "Maintenance Plan Expired",
            MessageBoxButtons.YesNo,
            MessageBoxIcon.Information
        );

        if (result == DialogResult.Yes)
        {
            OpenRenewalPage();
        }
    }

    private void OpenRenewalPage()
    {
        // Open renewal page with pre-filled activation key
        var lv = new LicenseValidator("settings.xml");
        string renewalUrl = $"https://yoursite.com/renew?key={lv.ActivationKey}";

        Process.Start(renewalUrl);
    }
}
Enter fullscreen mode Exit fullscreen mode

More details: Maintenance plan implementation

Grace Periods

Why Grace Periods Matter

The problem: Customer's maintenance expired 2 days ago. They want to download the latest update. Do you:

  • Block them? (frustrating)
  • Let them? (no revenue)
  • Offer a grace period? (balanced)

The solution: Grace period = extra days after expiry where updates still work.

Industry standards:

  • 30 days: Enterprise software
  • 14 days: SMB software
  • 7 days: Consumer software

Implementing Grace Periods

public class GracePeriodManager
{
    private const int GracePeriodDays = 30;

    public GracePeriodStatus CheckGracePeriod()
    {
        var lv = new LicenseValidator("settings.xml");

        DateTime maintenanceExpiry = lv.QlmLicenseObject.MaintenancePlanExpiryDate;
        DateTime gracePeriodEnd = maintenanceExpiry.AddDays(GracePeriodDays);

        bool isInGracePeriod = 
            DateTime.Now > maintenanceExpiry && 
            DateTime.Now <= gracePeriodEnd;

        int graceDaysRemaining = (gracePeriodEnd - DateTime.Now).Days;

        return new GracePeriodStatus
        {
            IsActive = isInGracePeriod,
            DaysRemaining = Math.Max(0, graceDaysRemaining),
            GracePeriodEnd = gracePeriodEnd,
            MaintenanceExpired = DateTime.Now > maintenanceExpiry
        };
    }

    public bool CanDownloadUpdates()
    {
        var maintenanceChecker = new MaintenanceChecker();
        var maintenanceStatus = maintenanceChecker.CheckMaintenanceStatus();

        // Allow if maintenance is active
        if (maintenanceStatus.IsActive)
            return true;

        // Allow if in grace period
        var graceStatus = CheckGracePeriod();
        if (graceStatus.IsActive)
        {
            ShowGracePeriodWarning(graceStatus.DaysRemaining);
            return true;
        }

        // Block - both expired
        return false;
    }

    private void ShowGracePeriodWarning(int daysRemaining)
    {
        MessageBox.Show(
            $"⚠️ Grace Period Active\n\n" +
            $"Your maintenance plan has expired.\n" +
            $"Grace period remaining: {daysRemaining} days\n\n" +
            "You can still download updates during the grace period.\n" +
            "Please renew your maintenance plan to continue receiving updates.",
            "Maintenance Grace Period",
            MessageBoxButtons.OK,
            MessageBoxIcon.Warning
        );
    }
}

public class GracePeriodStatus
{
    public bool IsActive { get; set; }
    public int DaysRemaining { get; set; }
    public DateTime GracePeriodEnd { get; set; }
    public bool MaintenanceExpired { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Version Entitlement

Controlling Which Versions Customers Can Download

Scenario: Customer bought v1.0 with 1 year maintenance. They renewed for year 2. What versions can they download?

Answer: Any version released while their maintenance was active.

public class VersionEntitlementChecker
{
    public bool CanDownloadVersion(
        string requestedVersion,
        DateTime versionReleaseDate)
    {
        var lv = new LicenseValidator("settings.xml");

        // Get original purchase date
        DateTime purchaseDate = lv.QlmLicenseObject.ActivationDate;

        // Get maintenance expiry date
        DateTime maintenanceExpiry = lv.QlmLicenseObject.MaintenancePlanExpiryDate;

        // Check if version was released during active maintenance
        bool wasMaintenanceActive = 
            versionReleaseDate >= purchaseDate &&
            versionReleaseDate <= maintenanceExpiry;

        if (!wasMaintenanceActive)
        {
            ShowVersionNotEntitledDialog(
                requestedVersion,
                versionReleaseDate,
                maintenanceExpiry
            );
            return false;
        }

        return true;
    }

    private void ShowVersionNotEntitledDialog(
        string version,
        DateTime releaseDate,
        DateTime maintenanceExpiry)
    {
        MessageBox.Show(
            $"Version {version} was released on {releaseDate:yyyy-MM-dd}.\n\n" +
            $"Your maintenance plan expired on {maintenanceExpiry:yyyy-MM-dd}.\n\n" +
            "To download this version, please renew your maintenance plan.",
            "Version Not Available",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

More info: QLM version entitlement

Automated Maintenance Renewals

E-Commerce Integration

QLM integrates with e-commerce platforms to automatically extend maintenance when customers pay:

public class MaintenanceRenewalIntegration
{
    // Called by e-commerce webhook (FastSpring, Stripe, PayPal)
    public bool ProcessMaintenanceRenewal(
        string activationKey,
        int years = 1)
    {
        var lv = new LicenseValidator("settings.xml");

        string response;

        // Calculate new expiry date
        // If current maintenance active: extend from current expiry
        // If expired: extend from today
        DateTime currentExpiry = GetCurrentMaintenanceExpiry(activationKey);
        DateTime newExpiry = currentExpiry > DateTime.Now
            ? currentExpiry.AddYears(years)
            : DateTime.Now.AddYears(years);

        // Renew maintenance plan
        bool success = lv.QlmLicenseObject.RenewMaintenancePlan(
            webServiceUrl: lv.QlmLicenseObject.DefaultWebServiceUrl,
            activationKey: activationKey,
            newExpiryDate: newExpiry,
            response: out response
        );

        if (success)
        {
            SendRenewalConfirmationEmail(activationKey, newExpiry);
            return true;
        }

        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Supported e-commerce platforms:

  • WooCommerce
  • FastSpring
  • 2checkout
  • Stripe Checkout
  • PayPal
  • Shopify
  • Chargify
  • HubSpot
  • Cleverbridge
  • BlueSnap
  • MyCommerce
  • UltraCart

More details: FastSpring maintenance integration

Automatic Renewal Reminders

public class MaintenanceReminderService
{
    public void CheckAndSendReminders()
    {
        var lv = new LicenseValidator("settings.xml");

        // Get all licenses with expiring maintenance
        var expiringLicenses = GetLicensesExpiringInDays(30);

        foreach (var license in expiringLicenses)
        {
            SendRenewalReminder(license);
        }
    }

    private void SendRenewalReminder(LicenseInfo license)
    {
        int daysRemaining = (license.MaintenanceExpiry - DateTime.Now).Days;

        string emailBody = $@"
            Dear {license.CustomerName},

            Your maintenance plan for {license.ProductName} expires in {daysRemaining} days
            on {license.MaintenanceExpiry:yyyy-MM-dd}.

            Renew now to continue receiving:
            • Software updates
            • New features  
            • Bug fixes
            • Technical support

            Renew here: https://yoursite.com/renew?key={license.ActivationKey}

            Questions? Contact support@yourcompany.com
        ";

        SendEmail(license.CustomerEmail, "Maintenance Plan Renewal", emailBody);
    }
}
Enter fullscreen mode Exit fullscreen mode

Recommended reminder schedule:

  • 30 days before expiry
  • 7 days before expiry
  • 1 day before expiry
  • 7 days after expiry (grace period reminder)
  • 30 days after expiry (final reminder)

Learn more: Automated email notifications

Handling Expired Maintenance

Scenario 1: Updates Check

public class UpdateManager
{
    public void CheckForUpdates()
    {
        var maintenanceChecker = new MaintenanceChecker();
        var maintenanceStatus = maintenanceChecker.CheckMaintenanceStatus();

        if (!maintenanceStatus.IsActive)
        {
            // Check grace period
            var graceManager = new GracePeriodManager();
            var graceStatus = graceManager.CheckGracePeriod();

            if (graceStatus.IsActive)
            {
                // In grace period - allow but warn
                ShowGracePeriodUpdateDialog(graceStatus.DaysRemaining);
                DownloadAndInstallUpdates();
            }
            else
            {
                // Fully expired - block updates
                ShowMaintenanceExpiredDialog(maintenanceStatus.ExpiryDate);
            }
        }
        else
        {
            // Active maintenance - proceed normally
            DownloadAndInstallUpdates();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Scenario 2: Technical Support Request

public class SupportRequestValidator
{
    public bool CanAccessSupport(string activationKey)
    {
        var maintenanceChecker = new MaintenanceChecker();
        var status = maintenanceChecker.CheckMaintenanceStatus();

        if (status.IsActive)
        {
            return true;
        }

        // Check if expired recently (30 days grace for support)
        int daysSinceExpiry = (DateTime.Now - status.ExpiryDate).Days;

        if (daysSinceExpiry <= 30)
        {
            ShowSupportGracePeriodMessage(30 - daysSinceExpiry);
            return true;
        }

        ShowSupportExpiredMessage(status.ExpiryDate);
        return false;
    }

    private void ShowSupportExpiredMessage(DateTime expiryDate)
    {
        MessageBox.Show(
            $"Your maintenance plan expired on {expiryDate:yyyy-MM-dd}.\n\n" +
            "Technical support requires an active maintenance plan.\n\n" +
            "Options:\n" +
            "1. Renew your maintenance plan\n" +
            "2. Purchase a support incident\n" +
            "3. Check community forums",
            "Support Not Available",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Scenario 3: Feature Access

public class FeatureGating
{
    public bool CanAccessPremiumFeature(string featureName)
    {
        var lv = new LicenseValidator("settings.xml");

        // Check if license includes feature
        bool hasFeature = lv.QlmLicenseObject.HasFeature(featureName);

        if (!hasFeature)
        {
            return false;
        }

        // Check if maintenance is required for this feature
        bool requiresMaintenance = IsMaintenanceRequiredFeature(featureName);

        if (requiresMaintenance)
        {
            var maintenanceChecker = new MaintenanceChecker();
            var status = maintenanceChecker.CheckMaintenanceStatus();

            if (!status.IsActive)
            {
                ShowFeatureRequiresMaintenanceDialog(featureName);
                return false;
            }
        }

        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Maintenance Plan Pricing Strategies

Strategy 1: Percentage of License Price

License: $1,000
Maintenance: 20% = $200/year

Pros:
✅ Simple to calculate
✅ Industry standard
✅ Scales with product value

Cons:
❌ May be too expensive for low-end customers
Enter fullscreen mode Exit fullscreen mode

Strategy 2: Flat Rate

License: $1,000
Maintenance: $150/year (flat)

Pros:
✅ Predictable for customers
✅ Easy to communicate
✅ Lower barrier to renewal

Cons:
❌ Doesn't scale with product tiers
Enter fullscreen mode Exit fullscreen mode

Strategy 3: Tiered Pricing

Basic License ($500) → $100/year maintenance
Professional ($1,500) → $250/year maintenance  
Enterprise ($5,000) → $750/year maintenance

Pros:
✅ Fair across product tiers
✅ Encourages upgrades
✅ Maximizes revenue

Cons:
❌ More complex to manage
Enter fullscreen mode Exit fullscreen mode

Industry data:

  • Average maintenance: 18-22% of license price
  • Enterprise: 15-18% (volume discounts)
  • SMB: 20-25% (higher support costs)
  • Renewal rates: 60-75% for well-managed plans

Using Quick License Manager

Quick License Manager provides complete maintenance plan capabilities:

Maintenance plan management - creation and tracking

✅ Grace period support - configurable grace periods

✅ Version entitlement - control which versions customers can download

Automated renewals - e-commerce integration

Renewal reminders - scheduled email campaigns

✅ Self-service renewal - customer portal

✅ Maintenance analytics - track renewal rates

QLM Management Console - manual renewal processing

Download QLM and implement maintenance plans today.

Conclusion

Maintenance plans offer the best of both worlds:

For Customers:

  • Own the software (perpetual license)
  • Choose when to pay (not forced)
  • Get updates when needed
  • No risk of losing access

For Vendors:

  • Recurring revenue stream
  • Higher lifetime value than pure perpetual
  • Better than pure subscription (higher retention)
  • Control update access

The numbers don't lie:

Pure Perpetual (no maintenance):
5-year revenue: $1,000

Pure Subscription:
5-year revenue: $1,013 (with 30% churn)

Perpetual + Maintenance:
5-year revenue: $1,560 (with 40% renewal rate)
Enter fullscreen mode Exit fullscreen mode

Companies that implement maintenance plans see:

  • 60-75% renewal rates (vs 70% subscription retention)
  • 40-50% higher LTV than pure subscription
  • 50-60% lower churn than subscriptions
  • 2-3x higher Net Promoter Scores

The key is making renewal easy and valuable: automated reminders, seamless payment integration, and grace periods that give customers breathing room.

With Quick License Manager, you get comprehensive maintenance plan capabilities that maximize renewal revenue while keeping customers happy.

Resources


Do you use maintenance plans? What's your renewal rate? Share in the comments! 💬

Top comments (0)