> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mention-me.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SFTP Connection

> How to connect to the Mention Me SFTP server, including authentication, folder structure, and file format requirements

export const SignInNotice = ({message = "To see code snippets pre-filled with your partner code"}) => {
  return <div data-mm-sign-in style={{
    display: "none"
  }}>
      <Info>
        {message}, <a data-mm-sign-in-link href="https://app.mention-me.com/sign-in"><strong>sign in</strong></a>.
      </Info>
    </div>;
};

export const SFTPFolders = () => {
  const [sftp, setSftp] = useState(null);
  const extractSftp = data => {
    if (!data || !data.sftp) return null;
    const {inboundFolders, outboundFolders} = data.sftp;
    if (!inboundFolders.length && !outboundFolders.length) return null;
    return {
      inboundFolders,
      outboundFolders
    };
  };
  useEffect(() => {
    if (!document.cookie.includes("__client_uat")) return;
    const existing = extractSftp(window.__mmMerchantContext);
    if (existing) {
      setSftp(existing);
      return;
    }
    const handler = e => {
      const result = extractSftp(e.detail);
      if (result) setSftp(result);
    };
    window.addEventListener("mm-merchant-ready", handler);
    return () => window.removeEventListener("mm-merchant-ready", handler);
  }, []);
  const renderPath = folder => {
    const parts = folder.path.replace(/\/$/, "").split("/");
    const nest = (segments, depth) => {
      if (segments.length === 0) return null;
      const [head, ...rest] = segments;
      const isLast = rest.length === 0;
      const label = isLast && folder.description ? head + " — " + folder.description : head;
      return <Tree.Folder name={label} defaultOpen>
          {rest.length > 0 ? nest(rest, depth + 1) : null}
        </Tree.Folder>;
    };
    return <span key={folder.path}>{nest(parts, 0)}</span>;
  };
  if (sftp) {
    return <Tree>
        {sftp.inboundFolders.length > 0 ? <Tree.Folder name="Inbound (you → Mention Me)" defaultOpen>
            {sftp.inboundFolders.map(renderPath)}
          </Tree.Folder> : null}
        {sftp.outboundFolders.length > 0 ? <Tree.Folder name="Outbound (Mention Me → you)" defaultOpen>
            {sftp.outboundFolders.map(renderPath)}
          </Tree.Folder> : null}
      </Tree>;
  }
  return <Tree>
      <Tree.Folder name="client_account_slug" defaultOpen>
        <Tree.Folder name="merchant_slug" defaultOpen>
          <Tree.Folder name="demo" defaultOpen>
            <Tree.Folder name="folder_name" defaultOpen>
              <Tree.Folder name="data" />
            </Tree.Folder>
          </Tree.Folder>
          <Tree.Folder name="prod" defaultOpen>
            <Tree.Folder name="folder_name" defaultOpen>
              <Tree.Folder name="data" />
            </Tree.Folder>
          </Tree.Folder>
        </Tree.Folder>
      </Tree.Folder>
    </Tree>;
};

export const MerchantContext = () => {
  const PARTNER_STORAGE_KEY = "mm-docs-partner";
  const escapeHtml = str => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  const getStoredPartner = () => {
    try {
      const raw = localStorage.getItem(PARTNER_STORAGE_KEY);
      if (!raw) return null;
      const data = JSON.parse(raw);
      if (data && data.partnerCode) return data;
    } catch (e) {}
    return null;
  };
  const setStoredPartner = data => {
    try {
      localStorage.setItem(PARTNER_STORAGE_KEY, JSON.stringify(data));
    } catch (e) {}
  };
  const clearStoredPartner = () => {
    try {
      localStorage.removeItem(PARTNER_STORAGE_KEY);
    } catch (e) {}
  };
  const captureQueryParams = () => {
    const params = new URLSearchParams(window.location.search);
    const partner = params.get("partner");
    const name = params.get("name");
    if (partner) {
      const data = {
        partnerCode: partner
      };
      if (name) data.displayName = name;
      setStoredPartner(data);
      params.delete("partner");
      params.delete("name");
      const qs = params.toString();
      const clean = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
      window.history.replaceState(null, "", clean);
      return data;
    }
    return getStoredPartner();
  };
  const isLocal = typeof window !== "undefined" && window.location.hostname === "localhost";
  const API_BASE = isLocal ? "http://localhost:3001" : "https://app.mention-me.com";
  const SIGN_IN_BASE = isLocal ? "http://localhost:3001/sign-in" : "https://app.mention-me.com/sign-in";
  const MERCHANT_WINDOW_KEY = "__mmMerchantContext";
  const publishMerchant = data => {
    window[MERCHANT_WINDOW_KEY] = data;
    window.dispatchEvent(new CustomEvent("mm-merchant-ready", {
      detail: data
    }));
  };
  const clearMerchant = () => {
    delete window[MERCHANT_WINDOW_KEY];
  };
  const BADGE_ID = "mm-partner-badge";
  const showPartnerBadge = (partnerData, source) => {
    if (document.getElementById(BADGE_ID)) return;
    const nav = document.getElementById("navigation-items");
    if (!nav || !nav.parentNode) return;
    const badge = document.createElement("div");
    badge.id = BADGE_ID;
    badge.className = "mm-partner-badge";
    const hasName = partnerData.displayName && partnerData.displayName !== partnerData.partnerCode;
    const heading = hasName ? escapeHtml(partnerData.displayName) : escapeHtml(partnerData.partnerCode);
    const code = hasName ? escapeHtml(partnerData.partnerCode) : "";
    const codeStr = code ? ' <span class="mm-partner-badge-code">' + code + '</span>' : '';
    const resetTitle = source === "clerk" ? "Sign out and switch account" : "Clear personalisation";
    badge.innerHTML = '<div class="mm-partner-badge-inner">' + '<span class="mm-partner-badge-content">' + '<span class="mm-partner-badge-context">Personalised for </span>' + '<span class="mm-partner-badge-name">' + heading + '</span>' + codeStr + '</span>' + '<button class="mm-partner-badge-reset" title="' + resetTitle + '">&times;</button>' + '</div>';
    nav.parentNode.insertBefore(badge, nav);
    badge.querySelector(".mm-partner-badge-reset").addEventListener("click", e => {
      e.preventDefault();
      e.stopPropagation();
      if (source === "clerk" && window.Clerk && window.Clerk.signOut) {
        clearMerchant();
        const redirect = SIGN_IN_BASE + "?redirect_url=" + encodeURIComponent(window.location.href);
        window.Clerk.signOut().then(() => {
          window.location.href = redirect;
        }).catch(() => {
          window.location.reload();
        });
        return;
      }
      clearStoredPartner();
      window.location.reload();
    });
  };
  const removePartnerBadge = () => {
    const el = document.getElementById(BADGE_ID);
    if (el) el.remove();
  };
  const showNotices = () => {
    document.querySelectorAll("[data-mm-sign-in]").forEach(el => {
      el.style.display = "";
    });
    const redirect = SIGN_IN_BASE + "?redirect_url=" + encodeURIComponent(window.location.href);
    document.querySelectorAll("[data-mm-sign-in-link]").forEach(a => {
      a.href = redirect;
    });
  };
  const hideNotices = () => {
    document.querySelectorAll("[data-mm-sign-in]").forEach(el => {
      el.style.display = "none";
    });
  };
  const walkCodeBlocks = replacements => {
    document.querySelectorAll("pre code").forEach(codeEl => {
      let html = codeEl.innerHTML;
      if (replacements) {
        for (const [from, to] of replacements) {
          html = html.split(from).join(escapeHtml(to));
        }
      }
      const paramStyle = "color:inherit;filter:brightness(110%);font-weight:bold;text-decoration:underline;cursor:pointer";
      html = html.replace(/(\?|&amp;)([a-z_]+)=/g, (match, prefix, name) => {
        const slug = name.replace(/_/g, "-");
        const target = document.getElementById("param-" + slug);
        if (!target) return match;
        return prefix + '<a href="#param-' + slug + '" style="' + paramStyle + '">' + name + "</a>=";
      });
      codeEl.innerHTML = html;
    });
  };
  const observeTabSwitches = callback => {
    let processing = false;
    const observer = new MutationObserver(() => {
      if (processing) return;
      processing = true;
      setTimeout(() => {
        callback();
        processing = false;
      }, 50);
    });
    setTimeout(() => {
      const tabs = document.querySelector("[role='tablist']");
      if (tabs && tabs.parentElement) {
        observer.observe(tabs.parentElement, {
          attributes: true,
          subtree: true,
          childList: true
        });
      }
    }, 500);
  };
  const pollForClerk = (onReady, onTimeout) => {
    let n = 0;
    const tick = () => {
      if (window.Clerk && window.Clerk.loaded) {
        return onReady(window.Clerk);
      }
      if (++n < 40) {
        setTimeout(tick, 250);
      } else {
        onTimeout();
      }
    };
    setTimeout(tick, 100);
  };
  const fetchMerchantFromAPI = () => {
    return fetch(API_BASE + "/api/docs/context", {
      credentials: "include",
      cache: "no-store"
    }).then(res => res.ok ? res.json() : null);
  };
  useEffect(() => {
    let cancelled = false;
    const urlPartner = captureQueryParams();
    const onPersonalised = (merchant, source) => {
      if (cancelled) return;
      const replacements = [["YOUR_PARTNER_CODE", merchant.partnerCode]];
      if (merchant.displayName) {
        replacements.push(["YOUR_TRADING_NAME", merchant.displayName]);
      }
      hideNotices();
      showPartnerBadge(merchant, source);
      walkCodeBlocks(replacements);
      observeTabSwitches(() => walkCodeBlocks(replacements));
    };
    const onUnauth = () => {
      if (cancelled) return;
      clearMerchant();
      removePartnerBadge();
      showNotices();
      walkCodeBlocks(null);
      observeTabSwitches(() => walkCodeBlocks(null));
    };
    const tryUrlFallback = () => {
      if (cancelled) return;
      if (urlPartner) {
        onPersonalised(urlPartner, "url");
      } else {
        onUnauth();
      }
    };
    if (!document.cookie.includes("__client_uat")) {
      tryUrlFallback();
      return;
    }
    pollForClerk(clerk => {
      if (cancelled) return;
      if (!clerk.session) {
        tryUrlFallback();
        return;
      }
      fetchMerchantFromAPI().then(data => {
        if (data) {
          publishMerchant(data);
          onPersonalised(data, "clerk");
        } else {
          tryUrlFallback();
        }
      }).catch(() => tryUrlFallback());
    }, () => {
      if (!cancelled) tryUrlFallback();
    });
    return () => {
      cancelled = true;
    };
  }, []);
  return null;
};

<MerchantContext />

## Overview

Mention Me provides an SFTP server for exchanging data files. This page describes how to connect, the folder structure, and requirements for data transfer.

<Note>
  SFTP must be enabled for your account before you can connect. Contact your Client Success Manager if SFTP is not yet set up. See also: [SFTP Setup](/knowledge/integrations/data-feeds/sftp-setup) for configuring authentication in the Mention Me platform.
</Note>

## Authentication

To connect to the Mention Me SFTP server, you need:

1. **One or more SSH public keys** — provided via the [Mention Me platform](https://mention-me.com/merchant/~/sftp/)
2. **One or more IP address ranges** — must be well-defined and restricted; broad cloud provider ranges (e.g. AWS) cannot be whitelisted

## Connection Details

| Setting            | Value                                              |
| ------------------ | -------------------------------------------------- |
| **Host**           | `sftp.mention-me.com`                              |
| **Username**       | Your client account slug (visible in the platform) |
| **Authentication** | SSH key                                            |

Example connection command (Linux/Mac):

```bash theme={null}
sftp -i /path/to/private.key your-account-slug@sftp.mention-me.com
```

You must connect from an IP address range that has been authorised for your account.

## Folder Structure

<SignInNotice message="To see your SFTP folder structure" />

<SFTPFolders />

<Note>
  Test or non-production data must always be placed within the `demo` tree. Production data must only be placed within the `prod` tree. Placing test data in `prod` (or vice versa) may result in unexpected behaviour such as customers being emailed or additional commission fees being charged.
</Note>

Data to be received by Mention Me, or data extracted by Mention Me for you, must be placed within the `data` subfolder for the relevant data format.

## Data Retention

Files uploaded to the SFTP server are retained for **7 days** before being purged. Pick them up (or replace them) within that window.

## Optional File Encryption

For added security, files may be encrypted with OpenPGP before upload. Mention Me's public key is published at [https://mention-me.com/keys/public\_batch.key](https://mention-me.com/keys/public_batch.key). If you would like to encrypt files this way, let your Client Success Manager know so we can enable decryption on the inbound side.

## Ingest File Format

Mention Me is strict about the format of data that can be ingested. See [SFTP Data Formats](/developer-docs/integration/sftp-data-formats) for full documentation on accepted file formats.

## Export File Format

Discuss with your Client Success Manager which data feeds you'd like to receive from Mention Me and we can provide example files.
