Omaship

February 19, 2026 · 14 min read

GDPR Compliance for Rails SaaS in 2026: The Practical Guide

Jeronim Morina

Jeronim Morina

Founder, Omaship

Every SaaS starter kit ships with auth and Stripe. Almost none ship with GDPR compliance. That is a problem if you sell to anyone in the EU — which, if you are building on the internet, you do.

GDPR is not a checkbox. It is not a cookie banner. It is a set of data rights that your users have and that you are legally obligated to support. And if you are building a SaaS you plan to sell someday, compliance is not optional — it is what separates a weekend project from a real business that passes due diligence.

This guide covers what GDPR actually requires from a Rails SaaS, how to implement each requirement in Rails 8, and what you can safely defer until later. No legalese. Just code and architecture decisions.

What GDPR actually requires from your SaaS

Strip away the legal language and GDPR boils down to six data rights your users have:

  • Right to access (Article 15): Users can request a copy of all data you hold about them. All of it — not just their profile, but session logs, payment history, analytics events, everything.
  • Right to rectification (Article 16): Users can correct inaccurate data. In practice, this means editable profiles and a way to request corrections for data they cannot edit themselves.
  • Right to erasure (Article 17): The "right to be forgotten." Users can request deletion of their data. You must comply within 30 days, with limited exceptions.
  • Right to data portability (Article 20): Users can request their data in a machine-readable format (JSON or CSV). This overlaps with the right to access but specifically requires a structured, commonly used format.
  • Right to object (Article 21): Users can opt out of processing for marketing, profiling, or legitimate interest purposes. This is why "unsubscribe" links exist.
  • Right to restrict processing (Article 18): Users can request that you stop processing their data while a dispute is resolved, without deleting it.

Most SaaS apps need to implement the first four. The last two matter once you do marketing automation or profiling. Let us focus on what you need from day one.

Data export: give users everything you have on them

This is the one most starter kits skip entirely. A user asks for their data, and you have to manually run SQL queries, zip up files, and email them. That does not scale, and it does not meet the 30-day response window when you have hundreds of users.

The architecture is straightforward in Rails:

  • Create a DataExport model that tracks export requests: user, status (pending/processing/completed/expired), requested_at, completed_at, download_url, expires_at.
  • Build a background job that collects all user data across your models, serializes it to JSON, and generates a downloadable file.
  • Add a "Request my data" button in account settings that creates a DataExport record and enqueues the job.
  • Email the user when their export is ready, with a time-limited download link (7 days is standard).

The tricky part is knowing which models contain user data. In a typical Rails SaaS, that includes:

  • User profile (name, email, avatar, settings)
  • Sessions (IP addresses, user agents, login timestamps)
  • Audit logs (actions taken, resources accessed)
  • Payment records (transactions, invoices, refunds)
  • Subscription history
  • Analytics events (if you store them server-side)
  • Support tickets or messages
  • Uploaded files and their metadata
  • Consent records (what they agreed to and when)

A practical pattern: add a Exportable concern to every model that holds user data. This concern defines a export_for_user(user) class method that returns the relevant records in a serializable format. Your export job then calls export_for_user on each exportable model and assembles the complete export.

# app/models/concerns/exportable.rb
module Exportable
  extend ActiveSupport::Concern

  class_methods do
    def export_for_user(user)
      raise NotImplementedError, "#{name} must implement .export_for_user"
    end
  end
end

# app/models/session.rb
class Session < ApplicationRecord
  include Exportable

  def self.export_for_user(user)
    where(user: user).map do |session|
      {
        created_at: session.created_at,
        ip_address: session.ip_address,
        user_agent: session.user_agent
      }
    end
  end
end

This pattern scales. When you add a new model that holds user data, you add the concern and implement the method. Your export job does not need to change.

Right to deletion: actually deleting data is harder than you think

"Just delete the user" sounds simple until you realize that user.destroy might cascade into payment records you are legally required to keep, audit logs that serve a legitimate business purpose, or shared resources that other users depend on.

The approach that works:

  • Separate PII from business records. Your payment model can keep the transaction amount, date, and status. But the user's name and email on the invoice? Those get anonymized or removed.
  • Use soft deletion for the user record. Set a deleted_at timestamp and anonymize PII fields (name → "Deleted User", email → a hash). This preserves referential integrity while removing personal data.
  • Hard-delete what you can. Sessions, analytics events, uploaded files, and most user-generated content can be fully deleted.
  • Document your retention policy. GDPR allows keeping data that is legally required (invoices for tax purposes, typically 7-10 years depending on jurisdiction). But you must document why you keep it and for how long.
# app/services/account_deletion_service.rb
class AccountDeletionService
  def initialize(user)
    @user = user
  end

  def call
    ActiveRecord::Base.transaction do
      # Hard-delete ephemeral data
      @user.sessions.destroy_all
      @user.notifications.destroy_all
      @user.analytics_events.destroy_all

      # Anonymize payment records (keep for tax compliance)
      @user.payments.update_all(
        customer_name: "Deleted User",
        customer_email: nil
      )

      # Anonymize and soft-delete the user
      @user.update!(
        name: "Deleted User",
        email: "deleted-#{@user.id}@anonymized.invalid",
        deleted_at: Time.current
      )
    end
  end
end

Consent management: not just a cookie banner

Cookie consent is the visible tip of a larger iceberg. GDPR requires consent to be freely given, specific, informed, and unambiguous. Practically, this means:

  • Record what was consented to. Not just "accepted cookies" but specifically which categories: analytics, marketing, functional. Store this as a consent record with a timestamp.
  • Make consent revocable. Users must be able to withdraw consent as easily as they gave it. A "cookie settings" link in the footer that lets them change their choices.
  • Do not bundle consent. "Accept all or leave" is not valid consent under GDPR. Users must be able to accept analytics but reject marketing, for example.
  • Track consent changes over time. Keep an audit trail. When regulators ask "did this user consent to marketing emails on this date?", you need a timestamped record.

For a Rails SaaS, a ConsentRecord model with user, category (analytics, marketing, functional), granted (boolean), and timestamp is sufficient. Store a new record on every consent change, do not just update a boolean flag. The history matters.

Encrypted attributes: protect data at rest

Rails has built-in encrypted attributes via Active Record's encrypts macro. Use them for any column that contains PII:

# app/models/user.rb
class User < ApplicationRecord
  encrypts :email, deterministic: true  # deterministic for lookups
  encrypts :name                         # non-deterministic for storage only
  encrypts :phone_number
end

Deterministic encryption lets you query by encrypted value (User.find_by(email: "...") still works). Use it for fields you need to search or look up. Non-deterministic encryption is more secure (same plaintext produces different ciphertext each time) but you cannot query by it. Use it for fields you only display.

Two things to get right:

  • Set up key rotation from the start. Rails supports multiple encryption keys via config.active_record.encryption.previous. If a key is compromised, you can rotate without re-encrypting everything immediately.
  • Keep encryption keys out of the database. Use Rails credentials or environment variables. If someone steals your database dump, the encrypted fields should be useless without the keys.

Audit logging: prove you did the right thing

Audit logs serve two purposes under GDPR: they help you detect unauthorized access, and they prove compliance when regulators ask. An audit trail that records who accessed what PII and when is essential.

  • Log sensitive actions: User login/logout, profile changes, password resets, data exports requested, deletion requests, admin impersonation, permission changes.
  • Include context: User ID, action, resource type and ID, IP address, timestamp. Do not log the actual data values (logging "user changed email" is fine; logging "email changed from [email protected] to [email protected]" creates a secondary PII store).
  • Make logs immutable. Append-only table or write-once storage. If someone can edit audit logs, they are useless as evidence.
  • Set a retention period. Audit logs contain IP addresses (PII under GDPR). Keep them long enough to be useful (1-3 years) but not forever.
# app/models/audit_log.rb
class AuditLog < ApplicationRecord
  belongs_to :user, optional: true

  # Prevent updates and deletions
  before_update { raise ActiveRecord::ReadOnlyRecord }
  before_destroy { raise ActiveRecord::ReadOnlyRecord }

  def self.record(user:, action:, resource: nil, ip_address: nil, metadata: {})
    create!(
      user: user,
      action: action,
      resource_type: resource&.class&.name,
      resource_id: resource&.id,
      ip_address: ip_address,
      metadata: metadata,
      created_at: Time.current
    )
  end
end

Third-party data processors: your weakest link

Every SaaS uses third-party services: Stripe for payments, PostHog for analytics, Resend for email, Sentry for error tracking. Under GDPR, you are responsible for how these processors handle your users' data.

  • Sign Data Processing Agreements (DPAs). Every major SaaS provider offers one. Stripe, PostHog, AWS — they all have DPA templates. Download them, sign them, store them. This is not optional.
  • Prefer EU-hosted or self-hosted services. PostHog can be self-hosted on your EU server. Hetzner datacenters are in Germany and Finland. This avoids the messy EU-US data transfer question entirely.
  • Document your data flow. A simple table: service name, what data it receives, where it is hosted, DPA signed (yes/no). Regulators love this. Acquirers love this even more.
  • Minimize what you send. Do not send user emails to your error tracking service if you do not need to. Use user IDs instead of names in analytics events. The less PII you share, the less liability you carry.

Privacy policy and data processing records

Your privacy policy is a legal requirement, but it is also a trust signal. A clear, readable privacy policy tells users and acquirers that you take data protection seriously.

  • List every category of data you collect and why. Be specific: "We collect your email address to send transactional emails and, with your consent, marketing updates."
  • Name your third-party processors. "We use Stripe (San Francisco, US) for payment processing under their DPA." Users have a right to know who handles their data.
  • State your retention periods. "Session data is deleted after 90 days. Payment records are retained for 10 years for tax compliance. Account data is deleted within 30 days of account deletion."
  • Provide contact information. An email address for privacy requests. If you are a larger company, name your Data Protection Officer.

For internal compliance, maintain a Record of Processing Activities (ROPA). Article 30 requires this for companies with more than 250 employees, but it is good practice regardless. A spreadsheet listing: processing activity, data categories, legal basis, recipients, transfers to third countries, and retention period. Acquirers will ask for this during due diligence.

Breach notification: hope for the best, plan for the worst

Under GDPR, you must notify your supervisory authority within 72 hours of discovering a personal data breach. If the breach poses a high risk to individuals, you must also notify affected users without undue delay.

  • Know your supervisory authority. For Germany, it is the state-level data protection authority (Landesdatenschutzbeauftragter). For other EU countries, check the EDPB list. Bookmark the reporting page now, not during a crisis.
  • Prepare a notification template. What happened, what data was affected, what you are doing about it, how users can protect themselves. Write this template before you need it.
  • Log everything security-relevant. Audit logs, access logs, deployment logs. During a breach investigation, you need to answer: what was accessed, when, and by whom. If you do not have logs, you cannot answer that.
  • Practice the response. A 15-minute tabletop exercise: "Our database was exposed. What do we do first?" Knowing the steps saves hours during an actual incident.

What you can defer vs. what you need on day one

Not everything is urgent. Here is a prioritized roadmap:

When What Effort
Day one Cookie consent with granular categories 2-3 hours
Day one Privacy policy with processor list 2-4 hours
Day one Encrypted attributes for PII columns 1 hour
Week one Account deletion (anonymize + soft-delete) Half day
Week one Audit logging for sensitive actions Half day
Month one Data export (background job + download) 1-2 days
Month one DPAs signed with all processors 2 hours
Before scaling Record of Processing Activities (ROPA) Half day
Before scaling Breach notification template + playbook 2 hours

GDPR as a competitive advantage

Here is what most founders miss: GDPR compliance is not just a legal obligation. It is a competitive advantage and an exit multiplier.

Enterprise sales: Every EU enterprise procurement questionnaire asks about GDPR. If you can answer "yes" to data export, deletion, encryption at rest, and audit logging from day one, you skip weeks of back-and-forth. Your competitors who bolted on compliance as an afterthought? They are still filling out the questionnaire while you are closing the deal.

Exit readiness: Acquirers run due diligence on your data handling. A clean GDPR posture — documented processors, consent records, deletion workflows, encrypted PII — signals a well-run business. It is the technical equivalent of clean books. Messy data handling, on the other hand, can tank a deal or reduce your multiple.

User trust: A clear privacy policy and easy-to-use data controls (export, deletion, consent settings) build trust. Trust reduces churn. In a market where every SaaS is competing on features, trust is an undervalued differentiator.

The best time to build GDPR compliance into your SaaS is at the start. The second best time is now. Architecture decisions made early (encrypted attributes, exportable concerns, soft-delete patterns) save weeks of refactoring later.

How Omaship handles privacy by design

Omaship is built by a German company (bloomed AI UG) on European infrastructure (Hetzner, Germany). Privacy is not an afterthought — it is the default:

  • Cookie consent with granular categories. Users choose analytics, marketing, and functional cookies independently. Consent records are timestamped and stored for audit.
  • EU-hosted infrastructure. Hetzner datacenters in Falkenstein and Helsinki. Your data does not leave the EU unless you choose to deploy elsewhere.
  • Rails encrypted attributes ready. The codebase structure supports Active Record encryption for PII columns from day one.
  • Privacy policy and imprint included. Not just placeholder text — actual GDPR-compliant pages that you customize for your business.
  • Brakeman security scanning in CI. Catches common security issues (which are often also privacy issues) before they reach production.
  • PostHog analytics with consent gating. Analytics events only fire after user consent. Server-side tracking means no third-party cookies.

The patterns in this guide — data export, account deletion, audit logging — are designed to work with Omaship's architecture. The Exportable concern pattern, the soft-delete service, and the immutable audit log all fit into the existing model structure without fighting the framework.

The bottom line

GDPR compliance for a Rails SaaS is not a multi-month project. It is a set of deliberate architecture decisions: encrypt PII, log sensitive actions, support data export and deletion, record consent, and document your data flows.

Rails makes most of this straightforward. Encrypted attributes, background jobs for exports, soft-delete patterns, and immutable audit logs — these are standard Rails patterns, not exotic compliance tooling. The framework was built for exactly this kind of structured, convention-driven development.

The founders who build compliance in from the start ship enterprise deals faster, get higher acquisition multiples, and sleep better. The founders who defer it end up bolting on compliance under pressure, with architectural decisions that fight the codebase they have already built.

Start with encrypted attributes, a cookie consent flow, and a privacy policy. Add account deletion and audit logging in week one. Build data export when you have your first 50 users. By the time procurement questionnaires arrive, you are ready.

Privacy by design, from day one

Omaship ships with cookie consent, EU-hosted infrastructure, encrypted attribute support, and GDPR-compliant privacy pages. Build on a foundation that passes due diligence.

Continue reading

We use analytics and session recordings to learn which parts of Omaship help and which need work. Accept all, or customize what you share.

Privacy policy