Data Retention & Deletion Policy
- Document owner:
- Kirk McCoy, Founder, PenSnap Software, LLC
- Effective date:
- 2026-05-21
- Last reviewed:
- 2026-05-21
- Next review due:
- 2027-05-21
- Version:
- 1.0
1. Purpose
This policy defines how PenSnap Software, LLC (“we,” “us,” operator of Partner Portal — the “Service”) retains, reviews, and deletes data we collect, process, or store on behalf of our users and our users' contacts. The policy implements the commitments made in our Privacy Policy and exists to satisfy:
- Industry-standard data-protection expectations (CCPA/CPRA, ColoradoPA, VCDPA, TDPSA, UCPA, CTDPA).
- Plaid's production-access security review (financial-data handling).
- Our own internal commitment to never hold data longer than it serves a legitimate business purpose.
2. Scope
This policy applies to all production data systems operated by PenSnap Software, LLC, including:
- Application databases (PostgreSQL hosted on Railway; secrets and tokens inside it are encrypted by us with AES-256-GCM).
- Application secret stores (Railway environment variables, encrypted in transit + at rest).
- Application logs (Railway log aggregation; vendor-side retention).
- Backups and disaster-recovery snapshots (Railway-managed).
- Third-party processors we route data through (Plaid, Resend, Twilio/RepLine, Google Maps, Anthropic).
3. Roles & responsibilities
| Role | Responsibility |
|---|---|
| Policy owner | Approves the policy + every revision. Currently: Founder. |
| Data protection lead | Operationalizes retention windows, builds enforcement crons, runs the periodic review. Currently: Founder. |
| Engineering | Implements deletion mechanisms in code + verifies they fire. |
| All users | Honor the policy in day-to-day use; report any data category that lacks a defined retention window. |
4. Data categories & retention windows
4.1. Authentication data
| Data | Retention | Deletion mechanism |
|---|---|---|
| Password hash (bcrypt) | While account exists | Hard-deleted on account deletion. |
| MFA secret (AES-256-GCM encrypted) | Until user disables 2FA or admin resets it | Cleared synchronously on disable / reset action. |
| MFA backup codes (bcrypt-hashed, legacy) | Dormant — no longer issued or accepted | Cleared on 2FA disable / reset; retained otherwise as inert historical data. |
| Session cookies | 30 days from issuance | Auto-expires; explicit logout clears immediately. |
| Mobile JWT tokens | 30 days from issuance | `tokenVersion` bump invalidates instantly. |
4.2. User account data
| Data | Retention | Deletion mechanism |
|---|---|---|
| Active user record | While account is `active = true` | N/A. |
| Deactivated user record | 30 days after `active = false` | Daily cron purges users with `deactivatedAt > 30d` AND zero downstream activity references. Users with history are retained for audit trail. |
| Account deletion request | Hard-deleted within 30 days of request | Manual admin action initiates; cron finalizes after 30-day grace period. |
4.3. Partner & contact records
| Data | Retention | Deletion mechanism |
|---|---|---|
| Active partner record | While the creator's account is active | N/A. |
| Archived partner (`archivedAt` set) | 365 days from archival | Annual cron purges archived partners past the window. |
| Partner after creator account closure | 30 days after the creator's account is hard-deleted, then purged | Cron sweep tied to user-deletion finalization. |
4.4. Financial account data (Plaid)
This section reflects our specific obligations under Plaid's Production Access Terms and our public Privacy Policy.
| Data | Retention | Deletion mechanism |
|---|---|---|
| Plaid access token (AES-256-GCM encrypted) | Until user disconnects the card | Cleared synchronously on disconnect action. |
| Plaid item id, account ids, last-4, cardholder name | Until disconnect + 90 days | Daily cron purges `LinkedCard` rows + cascades to all referenced data 90 days after `disconnectedAt`. |
| Card transactions (merchant, amount, date, Plaid category) | Until disconnect + 90 days | Same cron as above. |
| Webhook payloads from Plaid | 30 days | Daily cron purges raw payload rows older than 30 days. |
Special handling:
- We do not store full card numbers (PAN), CVV, PIN, or bank-account/routing numbers. These never enter our systems.
- On user-initiated disconnect, we immediately call Plaid's
/item/removeendpoint to invalidate the access token on Plaid's side before deleting our local copy. - Users may also revoke access directly via Plaid Portal (https://my.plaid.com); when Plaid notifies us of revocation, we follow the same 90-day purge for derived data.
4.5. Communications data
| Data | Retention | Deletion mechanism |
|---|---|---|
| Email message body (outbound, marketing) | Not stored — handed to Resend at send time | Resend retains per its own policy (~30 days). |
| Email message metadata (recipient, subject, status) | 90 days | Daily cron purges email-log rows older than 90 days. |
| SMS message body | Routed through RepLine; not stored in Partner Portal long-term | RepLine retains per its own policy. |
| SMS consent record (Partner.smsConsent) | Permanent for the lifetime of the partner record | Deleted only when the partner record is deleted. |
| Activity entries (call/email/visit notes) | Tied to the partner record's lifecycle | Deleted when the parent partner is deleted. |
4.6. Audit log
| Data | Retention | Deletion mechanism |
|---|---|---|
| Standard tenant audit entries | 24 months from creation | Weekly cron purges audit-log rows older than the window. |
| Super-admin / cross-tenant actions | Retained indefinitely | Excluded from the cron's WHERE clause. |
| Authentication failures (rate-limit hits) | 30 days | In-memory only; cleared on server restart. |
4.7. Operational / observability data
| Data | Retention | Deletion mechanism |
|---|---|---|
| Application stdout / stderr | Railway-managed (~30 days) | Railway log retention policy. |
| Error reports (via observability webhook) | Per third-party sink's policy | We pass through; do not duplicate locally. |
| Database backups | Per Railway backup schedule | Railway-managed; older snapshots auto-pruned. |
5. Enforcement
5.1. Automated mechanisms
Each retention cron writes an AuditLog entry summarizing rows purged so the policy owner can verify enforcement on demand.
5.2. Manual mechanisms
- Account deletion on user request. Submitted via the contact email in our Privacy Policy. Processed within 30 days; hard-deletes the user row + all data linked to them where retention windows don't dictate otherwise.
- Subject access requests (SAR). Processed within 30 days of verified request. Includes a JSON export of all data tied to the requestor's account.
- Right-to-be-forgotten requests (CCPA/CPRA/state equivalents). Processed within 30 days of verified request.
- Plaid-initiated revocation (user revokes via my.plaid.com). Plaid sends a webhook; we mark the connection disconnected and trigger the 90-day purge.
6. Data subject requests
| Data | Retention | Deletion mechanism |
|---|---|---|
| Access | Email the contact address listed in the Privacy Policy | 30 days |
| Correction | Email; we update the record | 30 days |
| Deletion (account or specific data) | Email; we hard-delete within the grace period | 30 days |
| Portability (data export) | Email; we provide a JSON export | 30 days |
| Opt-out of sale | N/A — we do not sell personal information | — |
| Opt-out of marketing email | Unsubscribe link in any marketing email | Immediate |
| Opt-out of SMS | Reply STOP | Immediate |
| Disconnect a financial account | "Disconnect" action in-app; or revoke via my.plaid.com | Immediate (token); 90 days (transaction data) |
All requests require identity verification (matching email-of-record + at least one additional factor for sensitive deletions).
7. Periodic review
This policy is reviewed at minimum annually, and additionally when:
- A new category of data is collected (new processor, new feature).
- A material change in applicable law (e.g., a new state privacy law, GDPR amendments).
- An incident or near-incident involving retention practices.
Each review records the date, reviewer, and any changes. The policy owner signs off on each new version.
8. Incident response
If a retention violation is discovered (data held past its window):
- Purge the offending data immediately as a one-off operation.
- Document the incident (cause, scope, count of rows, affected users).
- Fix the root cause (cron failure, missing enforcement) within 7 days.
- If the violation affected EU/UK residents in a way that triggers GDPR notification thresholds, notify the relevant supervisory authority within 72 hours.
- If affected individuals' rights are materially impacted, notify them directly.
9. Version history
| Version | Date | Author | Summary |
|---|---|---|---|
| 1.0 | 2026-05-21 | Kirk McCoy | Initial policy. Defines retention windows for all data categories. Plaid-specific section per Plaid Production Access requirements. |
10. Approval
This policy is in effect and binding on PenSnap Software, LLC's operation of the Partner Portal Service as of 2026-05-21.
The canonical source of this policy lives at docs/legal/data-retention-policy.md in our source repository. If this page and the markdown drift, the markdown is canonical; this route is regenerated from it on the next deployment.