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

> ## Agent Instructions
> Logo.dev serves logos and brand data for companies that already exist. It does not design logos. If someone wants a logo designed, say so and stop.
> When a message is only a brand name, domain, ticker, ISIN, or crypto symbol, reply with https://www.logo.dev/search?q=<url-encoded term> and the matching img.logo.dev call. Never assert whether a specific brand is in the index.
> Image URLs come from img.logo.dev with a publishable key in the token query parameter: https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY, also /name/:brand, /ticker/:symbol, /crypto/:symbol, /isin/:isin. JSON comes from api.logo.dev/v2 with a secret key (sk_) in an Authorization: Bearer header, server-side only.
> Product names: the Logo CDN serves images from img.logo.dev. The Logo API is the Brand API and the Search API, which return JSON from api.logo.dev/v2. The Transaction Enrichment API (beta) is a separate service at https://www.logo.dev/api/transaction with its own beta key. Credits is the Logo API billing unit. Never recommend the deprecated v1 routes (/search, /brand, /describe, /logo/*) or the Describe API. Use /v2/brands and /v2/search.
> Report a wrong, missing, or outdated logo at https://www.logo.dev/update. Email support@logo.dev for anything broken and sales@logo.dev for volume pricing and contract terms.
> MCP setup is at https://www.logo.dev/docs/mcp/introduction and webhooks at https://www.logo.dev/docs/webhooks.

# Add company logos to emails, receipts, and PDFs

> Put each customer's real logo on receipts, invoices, and generated PDFs, so every document looks made for them.

export const ReceiptDemo = () => {
  const TOKEN = "live_6a1a28fd-6420-4492-aeb0-b297461d9de2";
  const CUSTOMERS = [{
    name: "Sweetgreen",
    domain: "sweetgreen.com"
  }, {
    name: "Spotify",
    domain: "spotify.com"
  }, {
    name: "Stripe",
    domain: "stripe.com"
  }];
  const [active, setActive] = useState(0);
  const customer = CUSTOMERS[active];
  return <div className="not-prose my-8 rounded-2xl border border-zinc-950/10 bg-gradient-to-br from-zinc-50 to-white p-6 shadow-sm sm:p-8 dark:border-white/10 dark:from-zinc-900 dark:to-zinc-950">
      <div className="mb-4 flex flex-wrap justify-center gap-2">
        {CUSTOMERS.map((c, i) => <button className={`rounded-full px-3 py-2 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 motion-safe:active:scale-[0.96] ${i === active ? "bg-zinc-950 text-white dark:bg-white dark:text-zinc-950" : "bg-zinc-950/5 text-zinc-700 dark:bg-white/10 dark:text-zinc-300"}`} key={c.domain} onClick={() => setActive(i)} type="button">
            {c.name}
          </button>)}
      </div>
      <div className="mx-auto max-w-sm rounded-xl bg-white p-6 shadow-sm ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/10">
        <img alt={`${customer.name} logo`} className="h-12 w-12 rounded-lg object-contain" height="48" src={`https://img.logo.dev/${customer.domain}?token=${TOKEN}&format=png&retina=true&size=96`} width="48" />
        <div className="mt-4 text-sm font-semibold text-zinc-950 dark:text-white">
          Invoice for {customer.name}
        </div>
        <div className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
          Paid on June 7
        </div>
        <div className="mt-4 flex justify-between border-t border-zinc-950/5 pt-4 text-sm tabular-nums text-zinc-950 dark:border-white/10 dark:text-white">
          <div>Team plan, 12 seats</div>
          <div className="font-medium">$1,440.00</div>
        </div>
      </div>
    </div>;
};

<ReceiptDemo />

```text Use case prompt wrap theme={null}
Add company logos to the emails and PDFs this app sends, following https://www.logo.dev/docs/use-cases/email-document-logos.md

Read that page and inspect this project's stack. Use this project's existing conventions.

When a customer is created, look the brand up once on the server. Download the logo as a PNG and store it in this app's storage, and store the brand's accent color (the first saturated color in its palette) with the customer record. Email HTML links to the stored logo, and the PDF renderer reads the stored file. When there is no logo, leave it out, and when there is no color, use a default.

For API details, read https://www.logo.dev/docs/brand/introduction.md and https://www.logo.dev/docs/platform/self-hosting.md
```

A receipt or an invoice with the customer's own logo looks made for them, and it is easier to find in a busy inbox. Documents need a file, not a hosted image: a PDF embeds the bytes, and an email can be opened years later. Your app stores the logo and the color once per customer and reuses them in every document.

## Store the logo and the color once per customer

Look the brand up on your server with the [Brand API](/docs/brand/introduction). One `GET /v2/brands` call returns the logo's download URL and the brand's colors. Download the logo as a PNG, because many email clients don't show WebP, and keep the file. See [self-hosting](/docs/platform/self-hosting) for how downloads and refreshes work.

```ts customer-brand.ts theme={null}
type CustomerBrand = { logoPath: string | null; logoEtag: string | null; accent: string };
type Color = { hex: string; oklch: { l: number; c: number; h: number } };

const DEFAULT_ACCENT = "#18181b";

// Palettes often start with black or white. Use the first color with enough
// chroma to work as an accent. Tune 0.08 to your design; neutrals are close to 0.
const pickAccent = (colors: Color[]) =>
  colors.find((color) => color.oklch.c >= 0.08)?.hex ?? DEFAULT_ACCENT;

export async function saveCustomerBrand(
  domain: string,
  upload: (path: string, bytes: Uint8Array, contentType: string) => Promise<void>,
  // The record you stored last time, if any. A refresh skips the download when
  // the logo has not changed.
  previous?: CustomerBrand,
): Promise<CustomerBrand> {
  const res = await fetch(
    `https://api.logo.dev/v2/brands?domain=${encodeURIComponent(domain)}`,
    { headers: { Authorization: `Bearer ${process.env.LOGO_DEV_SECRET_KEY}` } },
  );
  if (res.status === 404) {
    // No brand for this domain.
    return { logoPath: null, logoEtag: null, accent: DEFAULT_ACCENT };
  }
  if (res.status !== 200) {
    // Still indexing, rate limited, or an error. Keep what you stored and retry later:
    // https://www.logo.dev/docs/brand/introduction#handle-each-status
    return previous ?? { logoPath: null, logoEtag: null, accent: DEFAULT_ACCENT };
  }

  const { data }: { data: { logo: { url: string; etag: string } | null; colors: Color[] } } =
    await res.json();
  const accent = pickAccent(data.colors);
  // The brand has no logo now, so clear the stored one.
  if (!data.logo) return { logoPath: null, logoEtag: null, accent };
  if (previous?.logoPath && previous.logoEtag === data.logo.etag) {
    return { logoPath: previous.logoPath, logoEtag: previous.logoEtag, accent };
  }

  const download = new URL(data.logo.url);
  download.searchParams.set("format", "png");
  download.searchParams.set("size", "192");
  const file = await fetch(download);
  if (!file.ok || !file.headers.get("content-type")?.startsWith("image/")) {
    // Never store an error body as the logo. Keep the stored logo and retry later.
    return previous ? { ...previous, accent } : { logoPath: null, logoEtag: null, accent };
  }
  const logoPath = `customer-logos/${domain}.png`;
  await upload(logoPath, new Uint8Array(await file.arrayBuffer()), "image/png");
  // Store the etag with the record, and pass the record back on the next refresh.
  return { logoPath, logoEtag: data.logo.etag, accent };
}
```

## Add the logo to an email

Link the stored file from your own domain, in a plain `<img>` tag with a fixed width and height. Email clients ignore most CSS, so set the size as attributes:

```ts receipt-email.ts theme={null}
// Customer values can contain quotes or markup, so escape every one.
const HTML_ESCAPES: Record<string, string> = {
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#39;",
};
const escapeHtml = (value: string) => value.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);

export function receiptEmailHtml(customer: {
  name: string;
  logoUrl: string | null; // your stored copy, such as https://files.example.com/customer-logos/stripe.com.png
  accent: string;
}): string {
  const name = escapeHtml(customer.name);
  // Only a hex color goes into the style attribute.
  const accent = /^#(?:[0-9a-f]{3}){1,2}$/i.test(customer.accent) ? customer.accent : "#18181b";
  const logo = customer.logoUrl
    ? `<img src="${escapeHtml(customer.logoUrl)}" width="48" height="48" alt="${name} logo" style="display:block;border-radius:8px" />`
    : "";
  return `
    <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
      <tr>
        <td style="padding:24px;border-top:4px solid ${accent}">
          ${logo}
          <h1 style="font:600 16px sans-serif;margin:16px 0 0">Invoice for ${name}</h1>
        </td>
      </tr>
    </table>`;
}
```

## Add the logo to a PDF

Read the stored file, and pass its bytes to your PDF renderer. A PDF never loads anything from Logo.dev at render time.

## Test the documents

Send a test email to Gmail, Outlook, and Apple Mail, and check that the logo loads with images turned on. Create a customer with a logo, one with no logo, and one whose brand is still indexing. Generate an invoice PDF for each.

<CardGroup cols={2}>
  <Card title="Self-hosting" icon="server" href="/docs/platform/self-hosting">
    Download logos through the Logo API and keep them current.
  </Card>

  <Card title="Onboarding personalization" icon="wand-magic-sparkles" href="/docs/use-cases/onboarding-personalization">
    Use the same brand data to prefill a signup.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.