DEV Community

Jayen Ashar
Jayen Ashar

Posted on • Originally published at pop3-importer.scaleupconsulting.com.au

Building a Secure Email Migration Tool: OAuth, Encryption, and Privacy by Design

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:

  1. Dual credential management: Need both POP3 passwords and Gmail access
  2. Sensitive data: Email content is highly personal/confidential
  3. Continuous access: Background services need long-term credential storage
  4. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Generate public/private key pair (one-time, server-side)
  2. Publish public key at /pop3-migrator.pub.asc
  3. Client fetches public key
  4. Client encrypts POP3 password in browser
  5. Encrypted password sent to server
  6. 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

  1. OAuth is worth the complexity: User trust is higher when they authenticate directly with Google
  2. Encrypt early, decrypt late: Minimize the window where sensitive data is accessible
  3. Privacy by deletion: Not storing data is better than securing stored data
  4. Fail loudly in development, notify users in production: Silent failures are UX disasters
  5. 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)