惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
爱范儿
爱范儿
The Cloudflare Blog
Y
Y Combinator Blog
B
Blog RSS Feed
Stack Overflow Blog
Stack Overflow Blog
博客园 - 叶小钗
G
Google Developers Blog
J
Java Code Geeks
P
Proofpoint News Feed
美团技术团队
Engineering at Meta
Engineering at Meta
腾讯CDC
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
WordPress大学
WordPress大学
博客园 - 聂微东
雷峰网
雷峰网
有赞技术团队
有赞技术团队
L
LangChain Blog
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 【当耐特】

Buttondown's blog

Email could have been X.400 times better The physicists who convinced Fermilab to send Brazil's emails Better in-app previews Analytics 3.0 Subscriber ID variables Comments! Send latest premium action Automation filtering Free API subscribers Surveys in automations Reply to replies Labels for RSS feeds How Jeremy Singer-Vine curates curious datasets for readers 2023 (and what's next) Email vs web content Sort by engagement Better gift subscriptions How Andy Dehnart built a career reviewing television New email template Email-based automations Opt-in reply tracking Automatic alt text More social network integrations Sort by metadata Overlarge image warnings Automation tag actions Pause emails mid-flight Search tags and automations Gift via automations Subscriber-driving emails
How we check every link in your email
Justin Duke · 2026-02-28 · via Buttondown's blog

One of the scariest parts about hitting "send" on an email is the feeling that there's no takesies-backsies. As soon as it starts hitting people's inboxes, you are left without a mechanism to fix any mistakes. A broken link in a blog post is a minor embarrassment; a broken link in an email to ten thousand subscribers is a small catastrophe.

This is why Buttondown checks every link in your email before you send it, and why the machinery behind that check is more involved than you might expect.

Our link checking infrastructure makes the most sense viewed through the lens of its development: just like in so many things, we started naively and grew increasingly robust over time.

---
config:
  layout: elk
  elk:
    mergeEdges: true
    nodePlacementStrategy: LINEAR_SEGMENTS
---

flowchart-elk
    A["Extract URLs from email body"] --> B{"Malformed?"}

    B -- "Yes" --> WARN
    B -- "No" --> D{"Cached?"}

    D -- "Hit (safe)" --> SAFE
    D -- "Hit (warn)" --> WARN
    D -- "Hit (block)" --> BLOCK
    D -- "Miss" --> F["HEAD https://foo.com"]

    F --> G{"2xx?"}
    G -- "Yes" --> L
    G -- "No (404/405)" --> I["GET https://foo.com"]
    I --> J{"2xx?"}
    J -- "Yes" --> L
    J -- "No / Timeout" --> WARN



    L["Check external services (Google Web Risk, SURBL, Spamhaus)"] --> M{"Flagged?"}
    M -- "No" --> SAFE
    M -- "Yes" --> BLOCK

    SAFE["Safe to send"] --> P
    WARN["Broken link warning"] --> P
    BLOCK["Block send"] --> P

    P[("Cache result (client + server)")]

    subgraph Client-Side
        A
        B
        D
    end

    subgraph Server-Side
        F
        G
        I
        J
        L
        M
    end

    classDef reject fill:#dc2626,stroke:#991b1b,color:#fff
    classDef success fill:#16a34a,stroke:#166534,color:#fff
    classDef warn fill:#ca8a04,stroke:#854d0e,color:#fff
    classDef process fill:#2563eb,stroke:#1e40af,color:#fff
    classDef cache fill:#9333ea,stroke:#6b21a8,color:#fff

    class BLOCK reject
    class SAFE success
    class WARN warn
    class F,I,L process
    class P cache

You might be wondering what actually processes the asynchronous work mentioned above. We used to use RQ exclusively (and still do in a handful of places), but this and other workloads have moved to AsynchronousAction, our home-built Postgres-based task runner. We'll write more about it soon!