When building tools that handle user credentials and email data, security isn't optional—it's fundamental. Recently, I built a POP3 to Gmail migration service, and I want to share the security architecture decisions that make it trustworthy.
The Security Challenges
Email migration tools face unique security challenges:
- Dual credential management: Need both POP3 passwords and Gmail access
- Sensitive data: Email content is highly personal/confidential
- Continuous access: Background services need long-term credential storage
- User trust: Users must trust you with their email history
Architecture Decisions
1. OAuth for Gmail Access
Decision: Use Google OAuth instead of asking for Gmail credentials.
Benefits:
- Service never sees user's Google password
- Users authenticate directly with Google
- Minimal permission scope (gmail.insert only)
- Automatic token refresh
- Users can revoke access anytime via Google Account settings
Implementation:
// Request minimal scope
const SCOPES = ['https://www.googleapis.com/auth/gmail.insert'];
// Users authenticate with Google, receive OAuth code
// Exchange code for tokens server-side
// Store refresh token encrypted
// Use for automated background sync
Key Insight: The gmail.insert scope allows adding messages but cannot read, modify, or delete existing email. This is the principle of least privilege in action.
2. Client-Side Credential Encryption
Challenge: Need to store POP3 passwords for background sync, but don't want plaintext passwords hitting the server.
Solution: Encrypt passwords in the browser before transmission.
Architecture:
- Generate public/private key pair (one-time, server-side)
- Publish public key at
/pop3-migrator.pub.asc - Client fetches public key
- Client encrypts POP3 password in browser
- Encrypted password sent to server
- Server decrypts only when needed for POP3 connection
Why This Matters:
- Network sniffing won't reveal passwords (already encrypted)
- Database breach won't reveal plaintext passwords
- Minimizes exposure window
3. Privacy by Design: Delete After Sync
Challenge: Storing email metadata creates privacy risks and compliance burdens.
Solution: Delete messages from POP3 server after successful sync to Gmail.
Benefits:
- No need to store what emails users have
- No message metadata in our database
- Reduces GDPR/privacy compliance surface area
- Users' email lives only in Gmail (single source of truth)
Trade-off:
- Cannot re-sync the same messages
- Users must accept deletion
Implementation Note: This isn't just a privacy feature—it also prevents duplicates if other migration tools are used in parallel.
4. Failure Transparency: Email Notifications
Challenge: Silent failures are terrible UX and leave users wondering if migration worked.
Solution: Send email notifications when messages fail to import.
Example Scenarios:
- Oversized messages (>10MB)
- Malformed messages
- Network failures
- API quota exceeded
Why This Matters:
Gmail's now-removed POP3 fetcher failed silently. Users didn't know what didn't sync. Email notifications give users visibility and control.
5. Minimal Data Collection
Principle: Only store what's necessary for the service to function.
What We Store:
- OAuth refresh tokens (encrypted)
- POP3 credentials (encrypted)
- Sync state (which messages already synced)
- Configuration (server, port, username)
What We DON'T Store:
- Email content
- Email metadata (subjects, senders, dates)
- User's email list
- Read/unread status
Implementation: Use message UIDs (unique identifiers from POP3 server) to track what's synced, but don't store the actual message data.
6. Network Security: SSRF Prevention
Challenge: Users provide hostnames. Malicious actors could target internal services.
Solution: Validate and block private IP ranges.
What We Block:
- Localhost (127.0.0.1, ::1)
- Private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Link-local addresses
- DNS rebinding attempts
Why It Matters: Prevents attackers from using your service to probe internal networks or attack localhost services.
Architecture Trade-offs
Security Through Obscurity?
Some might argue against publishing technical details. However:
- Pro: Obscurity adds a small security layer
- Con: Open architecture builds user trust
- Decision: Share high-level security model, omit specific implementation details (algorithm versions, library names, database schemas)
Stateless vs Stateful?
- Stateless: Better security, but can't resume after interruptions
- Stateful: Necessary for continuous sync, but requires secure state storage
- Decision: Stateful with encrypted state storage
Client-side vs Server-side Encryption?
- Client-side: Password never transmitted in plaintext
- Server-side: Simpler, but password exposed during transmission
- Decision: Client-side encryption for POP3 credentials
Lessons Learned
- OAuth is worth the complexity: User trust is higher when they authenticate directly with Google
- Encrypt early, decrypt late: Minimize the window where sensitive data is accessible
- Privacy by deletion: Not storing data is better than securing stored data
- Fail loudly in development, notify users in production: Silent failures are UX disasters
- Minimal scope creep: Only request permissions you actually need
Testing Security
Before launch, verify:
- [ ] OAuth flow works end-to-end
- [ ] Credentials encrypted before leaving browser (check network tab)
- [ ] SSRF prevention blocks private IPs
- [ ] Oversized messages handled gracefully
- [ ] Email notifications sent on failures
- [ ] Users can revoke access via Google Account settings
Conclusion
Building secure migration tools requires thinking about:
- Authentication: OAuth over password storage
- Encryption: Client-side before transmission
- Privacy: Delete data you don't need
- Transparency: Notify users of failures
- Least privilege: Minimal permission scopes
The result is a tool users can trust with their email history.
If you're building similar tools, I hope these patterns help. What security considerations have you faced in your projects?
Top comments (0)