# How Macro's Bidirectional @linking System Connects Docs, Messages, Tasks, and Emails

> Discover how Macro's bidirectional @linking system seamlessly connects docs messages tasks and emails using XML tags and JSON payloads for efficient cross-referencing.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: deep-dive
- Published: 2026-08-18

---

**Macro's bidirectional @linking system uses XML-style `<m-document-mention>` tags embedded with JSON payloads to create first-class references between any entities, storing symmetric link records in an `entity_mentions` table that enables fast reverse lookups.**

Every item in Macro—whether a Markdown document, channel message, task, or email thread—can reference any other item through a unified linking architecture. The system, implemented across the `macro-inc/macro` Rust codebase, treats connections as data rather than simple hypertext, enabling the backend to answer "what mentions this?" as efficiently as "what does this mention?"

## How the @linking System Works

At its core, the system relies on **XML-style tags** that wrap a minimal JSON payload. When users type `@` in the Macro interface, the frontend inserts a `<m-document-mention>` tag containing the target entity's UUID, type, and optional parameters. The backend's `mention_utils::parse` module then extracts these tags from raw text and persists bidirectional relationships to the database.

This design decouples the rendering layer from the storage layer. Because the tags travel with the content as plain text, any service—document storage, message queues, or email ingestion—can parse links without proprietary APIs.

## The Anatomy of an @link

The `<m-document-mention>` tag is the universal linking primitive. It appears in Markdown bodies, message text, and email content with a structure like this:

```rust
// Example payload structure from crates/prompt/src/mentions.rs
<m-document-mention>{
  "documentId": "a2f3c4d5-...",
  "blockName": "task",
  "blockParams": {}
}</m-document-mention>

```

The **`blockName`** field determines the entity type (`"md"` for documents, `"channel"` or `"chat"` for messages, `"task"` for tasks). The **`documentId`** holds the target UUID. This generic schema allows the parser in [`crates/mention_utils/src/parse.rs`](https://github.com/macro-inc/macro/blob/main/crates/mention_utils/src/parse.rs) to handle every entity type without type-specific logic:

```rust
// crates/mention_utils/src/parse.rs
const TAG_NAME: &str = "m-document-mention";

fn parse_mentions(input: &str) -> Vec<Mention> {
    // 1️⃣ Find every `<m-document-mention>` … `</m-document-mention>` block
    // 2️⃣ Pull the inner JSON payload
    // 3️⃣ Deserialize into MentionPayload { documentId, blockName, blockParams }
    // 4️⃣ Return Mention structs for persistence
}

```

## Storing Bidirectional Connections

Once parsed, links are materialized as **symmetric database records** in the `entity_mentions` table. Instead of a single directed edge, the system writes two rows: one from source to target, and one from target to source. This pattern, visible in [`crates/properties/src/domain/service_impl/task_properties.rs`](https://github.com/macro-inc/macro/blob/main/crates/properties/src/domain/service_impl/task_properties.rs), ensures that reverse lookups require only a simple indexed query rather than full-table scans.

The table schema stores:

- `source_entity_type` and `source_entity_id`: The entity containing the tag
- `target_entity_type` and `target_entity_id`: The referenced entity
- `created_at`: For chronological ordering

When establishing a link, services call helpers that insert both directions atomically:

```rust
// crates/properties/src/domain/service_impl/task_properties.rs
pub async fn link_parent_task(child_id: Uuid, parent_id: Uuid) -> Result<()> {
    // Forward link (child → parent)
    db::entity_mentions::create(&Mention {
        source_entity_type: "task",
        source_entity_id: child_id,
        target_entity_type: "task",
        target_entity_id: parent_id,
    }).await?;
    // Reverse link (parent → child)
    db::entity_mentions::create(&Mention {
        source_entity_type: "task",
        source_entity_id: parent_id,
        target_entity_type: "task",
        target_entity_id: child_id,
    }).await?;
    Ok(())
}

```

## Linking Specific Entity Types

### Documents

Documents use `<m-document-mention>` tags with `blockName: "md"` to reference other documents, tasks, or emails. The parser scans the raw Markdown content and creates `entity_mentions` rows linking the document ID to each target entity mentioned within it. The logic for document mention guidance resides in [`crates/prompt/src/mentions.rs`](https://github.com/macro-inc/macro/blob/main/crates/prompt/src/mentions.rs) and the serialization utilities in [`crates/mention_utils/src/serialize.rs`](https://github.com/macro-inc/macro/blob/main/crates/mention_utils/src/serialize.rs).

### Channel and Chat Messages

Messages embed the same `<m-document-mention>` tag but specify `blockName: "channel"` or `blockName: "chat"` in the payload, along with a `channel_message_id` in `blockParams`. When the message service processes incoming text, it extracts these tags and writes *message-mention* records to the `entity_mentions` table, allowing any mentioned document to surface the referencing message in its backlinks.

### Tasks

Task relationships—including parent-child hierarchies and cross-references—are managed through the **`task_properties`** service. The `entity_mentions` table stores these relationships so that a parent task knows all its subtasks and vice versa. Database helpers in [`crates/properties/src/outbound/task_property_queries.rs`](https://github.com/macro-inc/macro/blob/main/crates/properties/src/outbound/task_property_queries.rs) handle the insertion and retrieval of these bidirectional task links.

### Emails

Email threads connect to documents via the `document_email` table, defined in [`crates/macro_db_client/src/document/document_email.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/document/document_email.rs). When an email is saved with an attachment, the `create_document_email` function writes a row tying the `email_thread_id` to the `document_id`. This relationship is also represented in the generic `entity_mentions` view, enabling the email service to fetch all attached documents using the same reverse-lookup queries as other entity types.

## Querying Relationships in Reverse

Because every link exists in both directions, any entity can query its **backlinks**—the set of other entities that reference it. Services use a standardized query pattern against the `entity_mentions` table:

```rust
// crates/message_service/src/db.rs (excerpt)
pub async fn get_mentions_of_message(msg_id: Uuid) -> Result<Vec<Mention>> {
    sqlx::query_as!(
        Mention,
        r#"SELECT * FROM entity_mentions WHERE target_entity_id = $1"#,
        msg_id
    )
    .fetch_all(pool)
    .await
}

```

This query returns all documents, tasks, or other messages that mention the given message. The same pattern applies to documents querying their inbound references from tasks, or emails finding all documents that linked to them.

## Summary

- **Universal Tags**: The `<m-document-mention>` XML tag with JSON payload provides a generic linking primitive used across documents, messages, tasks, and emails.
- **Bidirectional Storage**: Every link is stored twice in `entity_mentions` (source→target and target→source), enabling efficient reverse lookups without scanning all content.
- **Centralized Parsing**: The `mention_utils::parse` module in [`crates/mention_utils/src/parse.rs`](https://github.com/macro-inc/macro/blob/main/crates/mention_utils/src/parse.rs) handles extraction for all entity types, making the system extensible to new entities by adding new `blockName` values.
- **Consistent Query Pattern**: Services retrieve backlinks with simple `WHERE target_entity_id = ?` queries against the `entity_mentions` table, regardless of whether the target is a document, message, task, or email.

## Frequently Asked Questions

### How does Macro parse @links from user-generated content?

The system passes raw text through `mention_utils::parse::parse_mentions`, which scans for `<m-document-mention>` tags, extracts the inner JSON, and deserializes it into a `MentionPayload` struct containing the target `documentId`, `blockName` (entity type), and `blockParams`. This parser is invoked by domain services handling document creation, message posting, task updates, and email ingestion.

### Can tasks reference documents and vice versa in Macro?

Yes. The `entity_mentions` table uses generic `source_entity_type` and `target_entity_type` columns that accept values like `"document"`, `"task"`, `"message"`, or `"email"`. This allows a task to mention a document, a document to mention a task, or any other combination. The `task_properties` service in [`crates/properties/src/domain/service_impl/task_properties.rs`](https://github.com/macro-inc/macro/blob/main/crates/properties/src/domain/service_impl/task_properties.rs) manages these cross-type links using the same bidirectional insertion pattern as intra-task relationships.

### Why does Macro store links bidirectionally instead of using a graph query?

Storing two directed edges (source→target and target→source) in the `entity_mentions` table transforms "who mentions me?" queries from expensive graph traversals into simple, indexed SQL lookups. This design prioritizes read performance for backlink panels and notification systems, accepting the minimal write-time cost of inserting two rows instead of one. The atomic insertion ensures consistency between both directions.

### Where is the email-to-document linking implemented?

Email thread attachments are linked to documents in [`crates/macro_db_client/src/document/document_email.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/document/document_email.rs). The `create_document_email` function writes rows to the `document_email` table, which is also represented in the `entity_mentions` view. This dual representation allows email services to use the same reverse-lookup queries as the rest of the platform to find all documents attached to a specific email thread.