Retention + deletion — ZevSend¶
Honest snapshot of where retention stands today. NDPA §31 requires data to be kept "no longer than necessary" — the substance of that rule applies to ZevSend now; the automated enforcement of it across every table is partial today and on the roadmap to formalise.
Current state — short version¶
A few targeted cleanups already run on a cron; the rest of the tables retain for the lifetime of the team / message / domain. When the DPO requests a full retention schedule, the windows will be agreed and the relevant cleanup crons added (mirroring ZevID's pattern).
What runs today¶
| Cleanup | Where | Cadence |
|---|---|---|
Expired-but-unchecked verifications → status failed |
VerifyJanitorService |
Every 5 minutes |
| Subscription renewal sweep (no PII deleted; invoices generated) | BillingRenewalService |
Daily 02:00 Africa/Lagos |
| Domain health re-check (no PII deleted; status transitions on stale DNS) | DomainHealthService |
Every 5 minutes |
No automated deletion of expired verification rows (the hash + envelope row stays around for the customer's check lookup window beyond the code's validity; it could be purged on a longer horizon — to be agreed).
What this means per category¶
| Category | Currently retained | Comment |
|---|---|---|
| Team + membership | Lifetime of the team | Hard delete is admin-only and rare; a deactivated team's rows stay |
| Domains | Lifetime of the team | Customer deletion through dashboard removes the row (and de-registers the SES identity); admin deletion is permission-gated and audited |
| Display-name applications | Lifetime of the team | Approved + withdrawn + rejected rows all retained for audit |
| Sender-ID applications (SMS) | Lifetime of the team | Same — full audit trail retained |
| Templates (team-owned) | Lifetime of the team | Archive ≠ delete; archived templates are hidden from the customer catalogue but still readable by historical send rows |
| API keys | Lifetime — revoked keys retained with revokedAt set |
Hash only; plaintext never persisted at any point |
| Send logs (email / SMS / WhatsApp messages) | Indefinite today | Operational deliverability + customer audit value; an ageing-out window will be agreed with the DPO |
| Verifications | Indefinite today | Hash only; plaintext OTP never persisted. Expired rows could be purged on a longer horizon |
| Suppressions | Indefinite | Required for anti-spam compliance — once a recipient has bounced or complained, the suppression stays unless the customer manually removes it |
| Verified recipients (sandbox-only) | Lifetime of the team | Sandbox-mode allowlist; deleted when the team is deleted |
| Webhook subscriptions | Lifetime of the team | Customer can delete a subscription at any time |
| Invoices + subscriptions + payment-method tokens | Retained — financial-record-adjacent | Expected to align with CBN 7-year financial-record retention when formalised |
| Admin audit logs | Retained — security-forensic | Expected long retention; will be agreed with DPO |
| ZPIP token + delivery audit | Retained — replay protection + audit | — |
Account deletion (customer-initiated)¶
When a customer asks to close their ZevSend team:
- The team owner can request deletion via the dashboard's account-settings page (in progress; until shipped, requests come through support and are actioned by admin).
- On deletion, the team's domains are de-registered from SES, API keys are revoked, templates are archived, and webhook subscriptions are disabled.
- Suppression list rows for the team are retained — they're regulatory operational metadata, not customer-controlled data. If a recipient on a suppressed list later signs up to a different team and that team wants to send to them, the suppression is per-team, not platform-wide, so it does not block the new relationship.
- Financial records (invoices, subscriptions) are retained per legal-obligation basis.
- Cross-product cascade: ZevSend writes a de-enrollment record back to ZevID.
Deletion via subject-rights request¶
See subject-rights.md. The DPO office is the entry point for formal NDPA erasure requests.
Backups¶
Neon's point-in-time recovery covers the database. The retention horizon of the PITR window is governed by the Neon plan we're on (the DPO can confirm with Daniel for the current value).
When this will be formalised¶
When the DPO requests a retention schedule with concrete windows + a deletion cron, engineering will:
- Agree windows per category with the DPO (special attention to send-log rows: balancing customer deliverability-debugging value against minimum-necessary retention).
- Implement a
cleanup.service.ts-style cron inzevsend-backend(mirroring the ZevID pattern). - Update this page with the cron's name, schedule, and what it deletes.
- Add a row to
change-log.md.