今日已更新 412 条资讯 | 累计 19972 条内容
关于我们

Building a Resume Download Gate: Email Collection, Signed Tokens, and an S3 Lesson

Vicente G. Reyes 2026年05月29日 11:32 3 次阅读 来源:Dev.to

I wanted a soft gate on my resume download. Not a paywall. Just an email field — enough friction to filter bots, enough signal to know who's interested. What started as a straightforward feature turned into a three-part lesson: stateless token signing, S3 public access, and email delivery mechanics. Here's the full story. The Feature The flow I wanted: Visitor clicks "Download Resume" on the About page or Hero A modal asks for their email Backend validates the email (format + disposable domain check) A signed, time-limited link is emailed to them They click the link, the PDF opens No database tokens. No cron jobs. No permanent S3 URLs floating around. Part 1 — The Model and the Gate The Resume Model Resume follows the singleton pattern I already use for page headers — force pk=1 on every save, restrict add/delete in admin. One row, forever. class Resume ( models . Model ): pdf = models . FileField ( upload_to = " resume/ " , storage = private_resume_storage ) last_updated = models . DateField ( default = date . today ) def save ( self , * args , ** kwargs ): self . pk = 1 super (). save ( * args , ** kwargs ) ResumeDownloadRequest logs every email that requests a link — no tokens, no expiry columns, just a record of who asked and when. class ResumeDownloadRequest ( models . Model ): email = models . EmailField () created_at = models . DateTimeField ( auto_now_add = True ) unsubscribed = models . BooleanField ( default = False ) class Meta : ordering = [ " -created_at " ] The unsubscribed flag is there for a future newsletter broadcast — when a new blog post goes out, skip anyone who opted out. Blocking Disposable Emails Before signing anything, the email is checked against a frozenset of ~70 known throwaway domains: # core/validators.py DISPOSABLE_EMAIL_DOMAINS : frozenset [ str ] = frozenset ({ " mailinator.com " , " guerrillamail.com " , " yopmail.com " , " 10minutemail.com " , " trashmail.com " , # ... ~70 total }) def is_disposable_email ( email : str ) -> bool

本文内容来源于互联网,版权归原作者所有
查看原文