links.md


title: "Links" description: "How MachineMail checks every link in inbound mail for mismatched text, lookalike and punycode domains, raw IPs, shorteners and brand impersonation." canonical: "https://machinemail.dev/docs/trust/links"

Links

How MachineMail checks every link in inbound mail for mismatched text, lookalike and punycode domains, raw IPs, shorteners and brand impersonation.

Source: https://machinemail.dev/docs/trust/links

Every link in a message gets an entry in trust.links. Links come from HTML anchors (<a> and <area>) and from bare http://, https:// and www. URLs in the text. Entries are deduplicated by target and text, up to 500 per message.

{
  "href": "http://198.51.100.23/paypal/login.php?session=8f7a6c",
  "text": "https://www.paypal.com/signin",
  "domain": "198.51.100.23",
  "display_mismatch": true,
  "shortener": false,
  "suspicious": true,
  "reasons": [
    "Unencrypted http link to a login/account page",
    "Link points to a raw IP address (198.51.100.23)",
    "PayPal login-style path on 198.51.100.23",
    "Link text says www.paypal.com, href is 198.51.100.23"
  ]
}

Links are analysed as written. MachineMail does not fetch them, so final_url is not currently set and no request ever reaches the sender's server because of a check.

Mismatched text: display_mismatch

When the visible text of a link looks like a URL or a domain, its domain is compared with the real destination. display_mismatch is true, and the link counts as suspicious, when:

Links through known email click-trackers (SendGrid, Mailchimp, Mandrill, HubSpot, Mailgun, Postmark, SparkPost, Customer.io, Substack, beehiiv, Amazon SES tracking, Proofpoint URL Defense, Outlook Safe Links and others) are expected to differ from their text and are not marked as mismatched.

Link text that is not a URL ("View it in your browser") is not compared. Subdomains of the same organisation (www.stripe.com text, pay.stripe.com target) match.

Structural red flags: suspicious

suspicious is true when any of these fire. Each adds a line to reasons:

Signal Example reason
javascript: or vbscript: URL javascript: URL
data: URL data: URL (can embed a full phishing page)
Unparseable URL Malformed URL
Scheme other than http, https, ftp Unusual scheme ...
Plain http to a login, verify, account, billing or password path Unencrypted http link to a login/account page
Raw IPv4 or IPv6 host Link points to a raw IP address (198.51.100.23)
Userinfo before the host, as in https://paypal.com@evil.example URL contains userinfo before the host (the real host is evil.example)
Port other than 80, 443, 8080, 8443 Non-standard port 8888
Punycode or mixed-script host Domain uses lookalike characters (pаypal.com imitates paypal.com) or Internationalised (punycode) domain ...
Four or more labels above the registrable domain Excessive subdomains (7 labels)
Hostname over 60 characters Very long hostname
A brand name in a domain the brand does not own PayPal name used in a domain it does not own (xn--pypal-4ve.com)
A brand name in a login-style path on someone else's domain PayPal login-style path on 198.51.100.23
A link inside hidden HTML Link is hidden from the human reader

mailto:, tel:, sms:, cid: and # fragment links are not checked for structural flags.

Brand impersonation

MachineMail keeps a list of brands commonly impersonated in phishing and the domains each one really uses: PayPal, Microsoft, Office 365, Outlook, OneDrive, Google, Gmail, Apple, iCloud, Amazon, Netflix, Facebook, Instagram, WhatsApp, LinkedIn, Dropbox, DocuSign, Adobe, Chase, Wells Fargo, Bank of America, Stripe, Coinbase, Binance, MetaMask, DHL, FedEx, USPS, HMRC, GitHub, Okta, Salesforce, Slack, Zoom, Venmo and Wise.

A brand name inside a host it does not own (paypal-secure-login.example, microsoft.account-verify.example) is flagged, and so is the brand's name in a credential-style path on a foreign domain. Hosts are compared after lookalike letters are normalised, so a Cyrillic а does not hide the brand. The same list is used by the classifier to catch a display name such as PayPal <service@paypa1-security.com>.

Shorteners

shortener is true for about fifty URL shorteners, including bit.ly, t.co, tinyurl.com, goo.gl, ow.ly, is.gd, rebrand.ly, cutt.ly, dub.sh, lnkd.in, amzn.to and youtu.be. A shortener alone does not make a link suspicious, but it adds URL shortener hides the destination to the link's reasons, and the classifier counts shortened links against a message that also asks for credentials or payment. For the message as a whole, a link through a shortener is evidence: it makes the message at least suspicious, however well the sender authenticated.

First-party shorteners and sibling domains

Some organisations shorten links in their own mail: t.co for X, lnkd.in for LinkedIn, youtu.be and g.co for Google and YouTube, amzn.to and a.co for Amazon, fb.me for Facebook, aka.ms for Microsoft. When the message passed DMARC aligned with one of that organisation's own domains, and the link names its destination in a url, u, target, redirect or dest parameter on one of that organisation's own domains, the link is first-party: shortener is false and the reasons say First-party link shortener of x.com to help.x.com. The destination is decoded and checked in its place. A bare short link (t.co/abc, lnkd.in/abc, fb.me/abc, aka.ms/abc) is still a shortener even in the organisation's own mail, because these organisations relay other people's posts and messages and their shorteners wrap whatever link those carry. youtu.be, g.co, amzn.to and a.co only ever redirect to their organisation's own sites, so in that organisation's aligned mail a bare link through them is first-party too. So is a link whose named destination is on someone else's site, and the same shortener in mail from anyone else or in mail that did not pass DMARC aligned. goo.gl is never first-party: anyone could create goo.gl links until 2019.

The same organisations' sibling domains count as the sender's own for link checks: a twitter.com or twimg.com link in mail from x.com is not an off-domain link. A free mail domain such as gmail.com is never treated as one of these organisations.

How links feed the verdict

Using link reports in an agent

An agent that browses should not follow a link from mail just because the text looks right:

const safeLinks = message.trust.links.filter((l) => !l.suspicious && !l.display_mismatch && !l.shortener);

Better still, only follow links from verified or known mail that the task needs, such as a sign-up confirmation you just triggered. See Using trust in agents.