Skip to content
Groundmail

Free email template

Receipt email template

A payment receipt with line items, tax, total and a link to the full invoice. Free and MIT-licensed, in React Email, MJML, HTML and plain text, with light and dark mode built in.

  • MIT license
  • Works with any email provider
  • Light and dark mode

What every receipt email needs.

This template does all six. The HTML version has one line item; for several, render the React component or repeat the row in the MJML.

  1. Line items people recognise

    What they paid for, in the words they'd use: plan name and period, add-ons, seats. A total on its own makes people check their bank statement.

  2. Subtotal, tax and total

    On separate lines. Finance teams need the tax figure, and the total should match the charge on the card to the cent.

  3. Receipt number, date and card

    The receipt number is what support searches for. Show the card as brand and last four digits, never the full number.

  4. A link to the full invoice

    The email is a summary. Link to the invoice page with billing address, tax IDs and a PDF, which is what accounting tools ask for.

  5. Your legal name and address

    Your company's legal name and registered address, plus the name on the card statement if it's different. The template's footer carries the name and address from theme.ts.

  6. Who to ask about the charge

    A reply line or support address for billing questions. A question answered by email is often a chargeback that never happens.

The code. Copy the format your stack uses.

Change the brand name, logo and colors once in theme.ts and every template follows.

import * as React from 'react'
import { Button, Details, Layout, P, Small, Title } from '../../components'
import type { TemplateMeta } from '../../components/meta'
import { plainText, textDetails } from '../../components/text'
import { theme } from '../../theme'

export type ReceiptItem = { description: string; amount: string }

export type ReceiptProps = {
  name: string
  receiptNumber: string
  paidAt: string
  paymentMethod: string
  items: ReceiptItem[]
  subtotal: string
  tax: string
  total: string
  receiptUrl: string
}

export const meta: TemplateMeta = {
  name: 'Receipt',
  description: 'A payment receipt with line items, tax, total and a link to the full invoice.',
  group: 'Billing',
  subject: `Your ${theme.brand.name} receipt #{{receipt_number}}`,
}

/**
 * Account templates only fill flat `{{variables}}`, so the hosted version has one
 * line item (`item_description`, `item_amount`). For several items, render this
 * component in your app and send the HTML, or send `items` with the React element.
 */
export const placeholders: Partial<ReceiptProps> = {
  items: [{ description: '{{item_description}}', amount: '{{item_amount}}' }],
}

export default function Receipt(props: ReceiptProps) {
  const { name, receiptNumber, paidAt, paymentMethod, items, subtotal, tax, total, receiptUrl } = props
  return (
    <Layout
      preview={`We received your payment of ${total}. Receipt #${receiptNumber}.`}
      footerNote="You're receiving this because you made a payment. Keep it for your records."
    >
      <Title>Thanks for your payment</Title>
      <P>
        Hi {name}, we received your payment of <strong>{total}</strong>. Here's your receipt.
      </P>
      <Details
        rows={[
          ['Receipt number', receiptNumber],
          ['Date paid', paidAt],
          ['Payment method', paymentMethod],
        ]}
      />
      <Details
        rows={[...items.map((item): [string, string] => [item.description, item.amount]), ['Subtotal', subtotal], ['Tax', tax]]}
        total={['Total paid', total]}
      />
      <Button href={receiptUrl}>View invoice</Button>
      <Small style={{ margin: 0 }}>Questions about this charge? Reply to this email and we'll help.</Small>
    </Layout>
  )
}

Receipt.PreviewProps = {
  name: 'Ada',
  receiptNumber: '2041-7730',
  paidAt: 'October 16, 2026',
  paymentMethod: 'Visa ending in 4242',
  items: [
    { description: 'Pro plan (monthly)', amount: '$40.00' },
    { description: 'Extra seats × 2', amount: '$10.00' },
  ],
  subtotal: '$50.00',
  tax: '$4.13',
  total: '$54.13',
  receiptUrl: 'https://example.com/billing/invoices/2041-7730',
} satisfies ReceiptProps

export function text(props: ReceiptProps) {
  const { name, receiptNumber, paidAt, paymentMethod, items, subtotal, tax, total, receiptUrl } = props
  return plainText(
    'Thanks for your payment',
    `Hi ${name}, we received your payment of ${total}. Here's your receipt.`,
    textDetails([
      ['Receipt number', receiptNumber],
      ['Date paid', paidAt],
      ['Payment method', paymentMethod],
    ]),
    textDetails([
      ...items.map((item): [string, string] => [item.description, item.amount]),
      ['Subtotal', subtotal],
      ['Tax', tax],
      ['Total paid', total],
    ]),
    `View invoice: ${receiptUrl}`,
    "Questions about this charge? Reply to this email and we'll help.",
  )
}

receipt.tsx, 99 lines, shown in full (scroll inside the panel). Typed props. Copy it with theme.ts and components/ from the repo. View on GitHub

Send it with Groundmail

Create the template in your account once, then send it by alias from your app.

1Terminal
npx -y --package=groundmail-cli -- groundmail templates push templates/receipt/receipt.html \
  --alias receipt \
  --name "Receipt" \
  --subject "Your Acme receipt #{{receipt_number}}" \
  --text templates/receipt/receipt.txt \
  --publish
2Node.js
import { Groundmail } from 'groundmail'

const groundmail = new Groundmail(process.env.GROUNDMAIL_API_KEY!)

await groundmail.emails.send({
  from: 'Acme <noreply@yourdomain.com>',
  to: 'ada@example.com',
  template: {
    id: 'receipt',
    variables: {
      name: 'Ada',
      receipt_number: '2041-7730',
      paid_at: 'October 16, 2026',
      payment_method: 'Visa ending in 4242',
      item_description: 'Pro plan (monthly)',
      item_amount: '$40.00',
      subtotal: '$50.00',
      tax: '$4.13',
      total: '$54.13',
      receipt_url: 'https://example.com/billing/invoices/2041-7730',
    },
  },
})

The CLI command and the Node.js send from the template's README, which also has Python, PHP and cURL. View on GitHub

Subject lines

Receipts get searched for months later, by product name and receipt number. Make both easy to find.

  • Your Acme receipt #{{receipt_number}}DefaultProduct, document type and number: everything a search needs.
  • Your Acme receipt for {{total}}GoodShows the amount before opening, which is what most people want to check.
  • Acme payment received: {{total}}GoodConfirms the payment went through, brand first.
  • Thanks!AvoidNothing to search for, and nothing to tell a receipt from a newsletter.

Receipts are billing records, so send them like it

People search for receipts months later, and teams forward them to accounting. They need to arrive every time and stay easy to find.

Google, Yahoo and Microsoft sender rules →
  • Send on a transactional stream, apart from marketing, so an unsubscribe never stops a receipt.
  • Send from your own domain with SPF, DKIM and DMARC set up.
  • Keep the from name and subject pattern the same every time, so search and inbox rules find them.
  • Groundmail sends this template by alias from your own domain, on our own mail servers. Send it with Groundmail

Questions

Is it free to use?

Yes. The templates are MIT-licensed: use them in commercial products and change them however you like.

Do I need Groundmail to use it?

No. The HTML and text files use plain placeholders that work with most providers' template engines and with Handlebars or Mustache. Sending through Groundmail just saves a step.

Does it work in dark mode and Outlook?

Yes. The full HTML file carries dark-mode styles for Apple Mail and Outlook.com, and the extra markup desktop Outlook needs to draw the button.

How do I change the colors and logo?

Edit theme.ts once (name, logo, address and colors for light and dark) and rebuild. Every template in the set picks it up.

Can the HTML version show more than one line item?

The ready-to-send HTML, the MJML and the hosted version have one line item ({{item_description}} and {{item_amount}}). For several, render the React Email component in your app, which takes an items array, or repeat the row in the MJML.