Hard-deleting stale contacts destroys their suppression history. If the same email address reappears in a future import, the system treats it as a net-new contact with no prior history — re-adding it to the active list even if the person previously bounced or unsubscribed. This directly violates CAN-SPAM § 5's requirement to honor unsubscribes permanently (suppression history is the evidence of compliance). Under GDPR Art. 17, the right to erasure is separate from list removal: even when personal data is erased, a suppression record (email hash or similar) must be retained to prevent the address from being re-added in future imports.
Low because hard-deletion of stale contacts typically removes a small fraction of the list at a time, but each deletion permanently destroys the suppression evidence needed for regulatory compliance.
Replace hard DELETE cleanup paths with status transitions that preserve the contact record and its suppression history:
-- Add status column if absent:
ALTER TABLE contacts ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'at_risk', 'inactive', 'lapsed', 'suppressed', 'pending_reverification'));
-- Stale cleanup: transition, never delete:
UPDATE contacts
SET status = 'pending_reverification'
WHERE last_engaged_at < now() - interval '180 days'
AND status = 'inactive';
For GDPR Art. 17 erasure requests where personal data must be deleted, retain a suppression shadow: after erasing the contact record, insert a row in a suppression_list table with the email (or a hash) and reason gdpr_erasure. This prevents re-import of erased addresses without retaining PII beyond what is needed.
ID: data-quality-list-hygiene.data-decay.stale-queue-not-delete
Severity: low
What to look for: Count all code paths that handle stale contacts and classify each as "soft-delete" or "hard-delete." Check what happens when a contact is identified as stale. The correct pattern is to queue them for re-verification or move them to a suppressed segment — not to delete them immediately. Deletion destroys suppression history and can cause the same address to reappear as "new" in a future import. Look for soft-delete patterns (deleted_at IS NULL) or a status transition to a pending_reverification state rather than a hard DELETE.
Pass criteria: Stale contacts are moved to a quarantine or re-verification segment with at least 1 soft-delete or status-transition path. No more than 0 stale-handling code paths use hard DELETE. Suppression history is preserved regardless of contact status.
Fail criteria: Stale contacts are hard-deleted from the database via at least 1 code path, wiping their suppression history.
Skip (N/A) when: Suppression is managed in a separate system (e.g., the ESP's global suppression list) that is not affected by contact record deletion.
Detail on fail: Example: "Stale contact cleanup job runs DELETE FROM contacts WHERE last_engaged_at < ... — suppression history destroyed on cleanup"
Remediation: Use soft-delete with status transitions:
-- Add status column if not present:
ALTER TABLE contacts ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'at_risk', 'inactive', 'lapsed', 'suppressed', 'pending_reverification'));
-- Stale cleanup: transition status, never hard-delete
UPDATE contacts
SET status = 'pending_reverification'
WHERE last_engaged_at < now() - interval '180 days'
AND status = 'inactive';
If compliance requirements (GDPR right to erasure) require deletion, maintain a separate suppression_list table for suppressed emails that persists even after the contact record is erased.