Back to blog
Article

Customer data export and the renewal call that follows

Customer data export and the renewal call that follows
S

StriveBit

4 min readSaaS Development

A self-serve export endpoint and the renewal call that follows

A logistics SaaS we maintain for a client in Noida has about 140 tenant organisations. Each one runs dispatch plans, vendor records, and three years of shipment history through the app. Around month eleven of their annual contract, a few customers start asking a version of the same question: if we leave, what happens to our data?

The honest answer used to be: we can dump it for you, give us a week. That answer made renewals harder. It signalled that the data was held rather than managed, and it gave the customer's IT team a reason to escalate the question internally before signing again.

We added a self-serve export endpoint. It took roughly four days of work, and it changed the tone of renewal conversations more than any feature we shipped that quarter.

The endpoint is unglamorous. It accepts a date range and a resource type, enqueues a job, and produces a signed URL pointing to a zipped set of CSVs on object storage. The CSVs are flat — shipments, vendors, line items, addresses — with foreign keys preserved so a downstream system can reconstruct the relationships. No nested JSON, no proprietary format.

The permission check is the part that matters in a multi-tenant setup. The export runs as the requesting user, not as a system admin. That means a warehouse manager can export shipment records for their own region but cannot pull vendor contracts from a tenant they do not belong to. We enforce this at the query layer, not just at the route.

@require_scope("data:export")
def create_export(request, tenant_id, resource, date_from, date_to):
    if request.user.tenant_id != tenant_id:
        raise Forbidden("cross-tenant export denied")
    if resource not in request.user.exportable_resources:
        raise Forbidden("resource not in scope")
    job = enqueue_export(
        tenant_id=tenant_id,
        user_id=request.user.id,
        resource=resource,
        date_from=date_from,
        date_to=date_to,
    )
    return {"job_id": job.id, "status_url": f"/exports/{job.id}"}

The job itself is a background worker that streams rows from the read replica, writes CSVs to a temp directory, zips them, uploads to S3, and sends an email with a link that expires in 72 hours. For a tenant with 200,000 shipment rows, the whole thing finishes in under two minutes. We cap the date range at 24 months per export to keep memory predictable, and customers who need everything can run two jobs.

There is a tradeoff here. A full export in a portable format makes it easier for a customer to leave. We accepted that. The alternative — making departure painful — is a weak retention strategy and a bad relationship. Customers who want to leave will leave; the question is whether they leave feeling respected or feeling locked in. That difference shows up in whether they recommend you to the next person they work with.

What we did not expect was how many customers used the export for reasons other than leaving. One tenant pulls a monthly export to feed an internal BI tool their IT team built. Another uses it as a backup snapshot before their quarterly audit. The export endpoint became a integration point, not just an exit door.

The renewal conversation shifted. When a customer asks about data portability, the answer is now a sentence: go to Settings, click Export, pick your date range. There is no week-long wait, no internal escalation, no sense that the vendor is reluctant. The question gets answered in the same call where pricing is discussed, and the call moves on.

We log every export in an audit table — who requested it, what resource, what date range, when the link was downloaded. Tenants with compliance requirements ask for that log. We give it to them as a CSV, naturally.

The feature is not technically interesting. It is a query, a zip file, and a signed URL. But it removed a source of friction that was quietly making renewals harder than they needed to be.

Back to all articles

Ready to build something great?

We help ambitious teams build software that lasts. If you're interested in working with us or want to discuss your project, let's connect.

Get in touch