Accepted values are stored in Tendril’s own data. They are not written to the contact card in Contacts.
Sources
If a card has no email address, only Name match runs, and the card explains why.
Gravatar
Gravatar is the first source because its data is shared with consent. People create their own profile and choose what to publish, and verified accounts only list services they’ve proven they own. That makes it more accurate than scraped data, and more appropriate to use. Tendril sends a SHA-256 hash of each email address on the card to the Gravatar profile API. The first address with a profile is used. A404 just means that address has no profile.
Signatures
A signature is written by the person themselves, it’s current, and it’s already in a mailbox Tendril is signed in to. That makes it the richest free source. It’s also the least structured, so every value from it is a guess. For each connected mailbox, Tendril finds up to 3 recent messages from the contact in the last 24 months. It reads the first 12 KB of each one without marking it as read. It removes quoted replies, then looks for the signature: after a-- line if there is one, otherwise at the end of the message.
A value that appears in more than one message counts as confirmed, so one unusual line doesn’t win.
Name match
Name match suggests email addresses for cards that don’t have one. It compares the names on your cards with the names Tendril has already seen in your mail headers and calendar invitations for people who have no card. It makes no requests, because the index already read those headers to build the Unsaved list. A match is not offered if:- The address is a role address like
info@,sales@,support@,noreply@orbuchhaltung@, including variants likesales-eu. - More than two different addresses are seen under the same name, so Tendril can’t tell which is the person.
- More than one card in your address book has that name.
The score never goes above 0.92, because names do collide. Anything below 0.5 isn’t offered. Each suggestion says what it’s based on, for example Name match — “Katya Novak” seen 14 times, last Aug 3, 2026.
Reviewing proposals
The Enrichment card on a contact lists open proposals by field, most confident first. Each one shows its source, and if it would replace a value already on the card, it shows the change.- Accept saves the value in Tendril. A new value replaces the one you accepted before for that field. For profiles and links, that’s one per service.
- Reject hides the proposal. Rejections are tied to the value itself, so a later lookup won’t suggest it again.
- If two sources suggest the same value, it appears once, credited to both, with the higher of their two confidence scores.
Enriching many contacts
The toolbar’s enrich button runs on the contacts currently shown, whether that’s everyone, a group or a search. It always asks first, because a bulk run is the one action in Tendril that sends requests out over and over. The confirmation sheet shows:- How many contacts will be checked, and how many were already checked. Those are skipped unless you turn on Re-check contacts already looked up.
- The limit of 250 contacts per run. Run it again to do the rest.
- The cost, which is currently always Free, and an estimated time.
Adding a source
A source implementsEnricher in Enrichment.swift:
EnrichSubject, a copy of the contact’s relevant fields, not the contact itself. That way it can’t reach the store or run on the main thread. It can only return proposals. To add it to runs, add it in EnrichmentEngine.makeEnrichers(). centsPerLookup exists so that a paid source can’t be added without its cost appearing in the confirmation sheet.