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

# Send invoices by email (Mailbox)

> Create draft e-invoices from emails with a UBL or PDF attachment, and monitor, download and retry those emails with the Mailbox API.

## Overview

Each company has a personal `@outbox.email.e-invoice.be` address. When you send an email with an invoice or [credit note](/guides/credit-notes) attachment (UBL XML or PDF) to this address, e-invoice.be makes a draft document from the attachment. The [Mailbox](/glossary#mailbox) is the record of these emails: it shows which emails arrived, which document each email made, and why an email failed.

Use the Mailbox when:

* Your ERP or accounting tool can email invoices but cannot call an API.
* You want to add the address in BCC to the emails that you already send to your customers.
* You want to start with e-invoicing before you build an API integration.

The email itself needs no API call. The Mailbox API is for monitoring: list the emails, read one email, download an attachment and retry a failed email.

<Note>
  The Mailbox is for documents that you send (outbound). To read documents that you receive through Peppol, see [Receive documents](/guides/receiving-documents).
</Note>

## Find the mailbox address

The address is unique for each company. You can get it in two ways:

* **In the app**: open the company settings in [app.e-invoice.be](https://app.e-invoice.be). The card **E-Invoice Email Conversion** shows the **BCC Email Address** with a copy button.
* **With the API**: call `GET /api/me/` and read the field `bcc_recipient_email`.

```bash cURL theme={null}
curl -X GET "https://api.e-invoice.be/api/me/" \
  -H "Authorization: Bearer $E_INVOICE_API_KEY"
```

```json Response (extract) theme={null}
{
  "company_name": "E-INVOICE BV",
  "company_tax_id": "BE1018265814",
  "bcc_recipient_email": "e-invoice-bv+k3x9q7m2@outbox.email.e-invoice.be"
}
```

If `bcc_recipient_email` is `null`, the Mailbox is not available for the company.

<Warning>
  Keep the address private. The part after the `+` identifies your company, and an email to this address makes a document in your company.
</Warning>

## Send an email to the Mailbox

<Steps>
  <Step title="Attach one invoice or credit note">
    Attach the document as UBL XML or as PDF. One email makes one document. For more than one invoice, send more than one email.
  </Step>

  <Step title="Add the mailbox address">
    Put the mailbox address in the BCC field (recommended), or in the To or CC field. With BCC, your customer does not see the address.
  </Step>

  <Step title="Send the email">
    The email shows in the Mailbox. Processing of a PDF can take some minutes, because the data is extracted from the PDF.
  </Step>

  <Step title="Review and send the draft">
    When the processing is complete, the email has a `document_id`. The document is in state `DRAFT`. Review it, then send it with `POST /api/documents/{document_id}/send`.
  </Step>
</Steps>

## What happens to an email

### Selection of the main attachment

| Attachments in the email | Result |
| - | - |
| One UBL XML file or one PDF file | This file is the main document. |
| UBL XML and PDF files | UBL has precedence. The PDF is not used to make the document. |
| More than one candidate of the same type | The file that is most likely the invoice is selected automatically, based on the file names. |
| No UBL XML and no PDF file | The email is not converted. |

The main attachment can be 10 MB at most.

<Tip>
  Send one invoice file for each email and give it a clear file name, for example `invoice-INV-2026-001.pdf`. Then the selection is not ambiguous.
</Tip>

### Other attachments

* **PDF as main attachment**: the PDF and the other attachments with a file type that Peppol permits (rule BR-CL-24: PDF, PNG, JPEG, CSV, XLSX, ODS) become attachments of the document. Other file types, and images that are part of the email body (for example a logo in a signature), are ignored.
* **UBL as main attachment**: only the UBL is used. Other attachments in the email are not added. Embed the attachments in the UBL itself. See [Attachments and PDF](/guides/attachments).

### Conversion and validation

* A UBL file is validated against Peppol BIS Billing 3.0. An invalid UBL makes the email fail.
* A PDF is converted in the same way as in [Create documents from PDF](/guides/pdf-documents). If the extracted data does not make a valid e-invoice, the email fails.
* A successful email makes one document in state `DRAFT`. The Mailbox does not send the document through Peppol. You send it yourself.

## Email status

The status of an email is not a field in the response. It is derived from `processed` and `error_message`. You can filter on it with the `status` parameter.

| Status | `processed` | `error_message` | Meaning |
| - | - | - | - |
| `pending` | `false` | `null` | The email arrived and is not processed yet. |
| `success` | `true` | `null` | The document was made. `document_id` has the ID of the document. |
| `failed` | any | text | The processing failed. `error_message` gives the cause. |

## List the emails

`GET /api/mailbox/` returns the emails of your company, with the newest email first.

<ParamField query="page" type="integer" default="1">
  Page number. Minimum 1.
</ParamField>

<ParamField query="page_size" type="integer" default="20">
  Number of emails for each page. Minimum 1, maximum 100.
</ParamField>

<ParamField query="status" type="string">
  Filter by status: `pending`, `success` or `failed`. Has precedence over `processed`.
</ParamField>

<ParamField query="processed" type="boolean">
  Filter by the `processed` field. Ignored when `status` is set.
</ParamField>

<ParamField query="received_from" type="string (date-time)">
  Inclusive lower limit of the received date, for example `2026-10-01T00:00:00Z`. If an email has no `received_at`, the filter uses `created_at`.
</ParamField>

<ParamField query="received_to" type="string (date-time)">
  Inclusive upper limit of the received date. If an email has no `received_at`, the filter uses `created_at`.
</ParamField>

<ParamField query="search" type="string">
  Case-insensitive search in the sender email address, the subject, the message ID and the attachment file names.
</ParamField>

<ParamField query="sort_by" type="string" default="received_at">
  Sort field: `received_at` or `created_at`.
</ParamField>

<ParamField query="sort_order" type="string" default="desc">
  Sort direction: `asc` or `desc`.
</ParamField>

The example lists the failed emails.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.e-invoice.be/api/mailbox/?status=failed&page=1&page_size=20" \
    -H "Authorization: Bearer $E_INVOICE_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    status: 'failed',
    page: '1',
    page_size: '20',
  });

  const response = await fetch(`https://api.e-invoice.be/api/mailbox/?${params}`, {
    headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
  });

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status} ${await response.text()}`);
  }

  const { items, total } = await response.json();
  console.log(`${total} failed emails`);
  for (const email of items) {
    console.log(email.id, email.subject, email.error_message);
  }
  ```

  ```python Python theme={null}
  import os

  import requests

  response = requests.get(
      "https://api.e-invoice.be/api/mailbox/",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      params={"status": "failed", "page": 1, "page_size": 20},
      timeout=30,
  )
  response.raise_for_status()

  data = response.json()
  print(f"{data['total']} failed emails")
  for email in data["items"]:
      print(email["id"], email["subject"], email["error_message"])
  ```

  ```php PHP theme={null}
  <?php
  $query = http_build_query([
      'status' => 'failed',
      'page' => 1,
      'page_size' => 20,
  ]);

  $ch = curl_init("https://api.e-invoice.be/api/mailbox/?$query");
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('E_INVOICE_API_KEY')],
  ]);

  $body = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($status !== 200) {
      throw new RuntimeException("Request failed: $status $body");
  }

  $data = json_decode($body, true);
  echo $data['total'] . " failed emails\n";
  foreach ($data['items'] as $email) {
      echo $email['id'] . ' ' . $email['subject'] . ' ' . $email['error_message'] . "\n";
  }
  ```

  ```csharp C# theme={null}
  using System.Net.Http.Headers;
  using System.Text.Json;

  using var client = new HttpClient { BaseAddress = new Uri("https://api.e-invoice.be") };
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  var response = await client.GetAsync("/api/mailbox/?status=failed&page=1&page_size=20");
  response.EnsureSuccessStatusCode();

  using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
  Console.WriteLine($"{json.RootElement.GetProperty("total").GetInt32()} failed emails");
  foreach (var email in json.RootElement.GetProperty("items").EnumerateArray())
  {
      Console.WriteLine($"{email.GetProperty("id")} {email.GetProperty("subject")} {email.GetProperty("error_message")}");
  }
  ```
</CodeGroup>

```json Response theme={null}
{
  "items": [
    {
      "id": "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i",
      "message_id": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817",
      "sender_email": "billing@e-invoice.be",
      "sender_name": "E-INVOICE BV",
      "to_addresses": "accounts@openpeppol.org",
      "cc_addresses": null,
      "bcc_addresses": "e-invoice-bv+k3x9q7m2@outbox.email.e-invoice.be",
      "subject": "Invoice INV-2026-001",
      "attachments": [
        {
          "filename": "invoice-INV-2026-001.pdf",
          "size": 184320,
          "content_type": "application/pdf"
        }
      ],
      "attachment_count": 1,
      "processed": false,
      "error_message": "Not able to generate valid e-invoice from 'invoice-INV-2026-001.pdf'",
      "received_at": "2026-10-01T09:14:22Z",
      "created_at": "2026-10-01T09:14:23Z",
      "processed_at": null,
      "document_id": null
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 20,
  "pages": 1,
  "has_next_page": false
}
```

### Response fields

| Field | Type | Description |
| - | - | - |
| `id` | string | ID of the email in the Mailbox. Starts with `mail-`. |
| `message_id` | string | Message ID that the mail system gave to the email. |
| `sender_email` | string | Email address of the sender. |
| `sender_name` | string or null | Display name of the sender. |
| `to_addresses`, `cc_addresses`, `bcc_addresses` | string or null | Recipients of the email. |
| `subject` | string or null | Subject of the email. |
| `attachments` | array | For each attachment: `filename`, `size` (bytes), `content_type`. |
| `attachment_count` | integer | Number of attachments. |
| `processed` | boolean | `true` when the document was made. |
| `error_message` | string or null | Cause of the failure. `null` when there is no failure. |
| `received_at` | date-time or null | Time at which the email arrived. |
| `created_at` | date-time | Time at which the Mailbox record was made. |
| `processed_at` | date-time or null | Time at which the processing was complete. |
| `document_id` | string or null | ID of the document that the email made. |

## Get one email

`GET /api/mailbox/{inbound_email_id}` returns one email. Use it to poll an email until `document_id` has a value or `error_message` has a value.

```bash cURL theme={null}
curl -X GET "https://api.e-invoice.be/api/mailbox/mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i" \
  -H "Authorization: Bearer $E_INVOICE_API_KEY"
```

```json Response theme={null}
{
  "id": "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i",
  "message_id": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817",
  "sender_email": "billing@e-invoice.be",
  "sender_name": "E-INVOICE BV",
  "to_addresses": "accounts@openpeppol.org",
  "cc_addresses": null,
  "bcc_addresses": "e-invoice-bv+k3x9q7m2@outbox.email.e-invoice.be",
  "subject": "Invoice INV-2026-001",
  "attachments": [
    {
      "filename": "invoice-INV-2026-001.pdf",
      "size": 184320,
      "content_type": "application/pdf"
    }
  ],
  "attachment_count": 1,
  "processed": true,
  "error_message": null,
  "received_at": "2026-10-01T09:14:22Z",
  "created_at": "2026-10-01T09:14:23Z",
  "processed_at": "2026-10-01T09:15:41Z",
  "document_id": "doc-4n7c2x9v1b5m8k3j6h0g2f4d7s1a9q5w"
}
```

An ID that does not exist, or that belongs to a different company, gives `404` with `{"detail": "Inbound email not found"}`.

## Download an attachment

`GET /api/mailbox/{inbound_email_id}/attachments/{filename}` returns the original file from the email. The response body is the binary content, with the `Content-Type` of the attachment and a `Content-Disposition: attachment` header.

The `filename` must be exactly the same as `attachments[].filename` in the email. URL-encode the file name.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.e-invoice.be/api/mailbox/mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i/attachments/invoice-INV-2026-001.pdf" \
    -H "Authorization: Bearer $E_INVOICE_API_KEY" \
    --output invoice-INV-2026-001.pdf
  ```

  ```javascript Node.js theme={null}
  import { writeFile } from 'node:fs/promises';

  const emailId = 'mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i';
  const filename = 'invoice-INV-2026-001.pdf';

  const response = await fetch(
    `https://api.e-invoice.be/api/mailbox/${emailId}/attachments/${encodeURIComponent(filename)}`,
    { headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` } },
  );

  if (!response.ok) {
    throw new Error(`Download failed: ${response.status} ${await response.text()}`);
  }

  await writeFile(filename, Buffer.from(await response.arrayBuffer()));
  console.log(`Saved ${filename} (${response.headers.get('content-type')})`);
  ```

  ```python Python theme={null}
  import os
  from urllib.parse import quote

  import requests

  email_id = "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i"
  filename = "invoice-INV-2026-001.pdf"

  response = requests.get(
      f"https://api.e-invoice.be/api/mailbox/{email_id}/attachments/{quote(filename)}",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      timeout=60,
  )
  response.raise_for_status()

  with open(filename, "wb") as file:
      file.write(response.content)
  print(f"Saved {filename} ({response.headers['Content-Type']})")
  ```

  ```php PHP theme={null}
  <?php
  $emailId = 'mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i';
  $filename = 'invoice-INV-2026-001.pdf';

  $url = "https://api.e-invoice.be/api/mailbox/$emailId/attachments/" . rawurlencode($filename);

  $ch = curl_init($url);
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('E_INVOICE_API_KEY')],
  ]);

  $content = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($status !== 200) {
      throw new RuntimeException("Download failed: $status $content");
  }

  file_put_contents($filename, $content);
  echo "Saved $filename\n";
  ```

  ```csharp C# theme={null}
  using System.Net.Http.Headers;

  var emailId = "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i";
  var filename = "invoice-INV-2026-001.pdf";

  using var client = new HttpClient { BaseAddress = new Uri("https://api.e-invoice.be") };
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  var response = await client.GetAsync(
      $"/api/mailbox/{emailId}/attachments/{Uri.EscapeDataString(filename)}");
  response.EnsureSuccessStatusCode();

  await File.WriteAllBytesAsync(filename, await response.Content.ReadAsByteArrayAsync());
  Console.WriteLine($"Saved {filename}");
  ```
</CodeGroup>

```http Response theme={null}
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice-INV-2026-001.pdf"; filename*=UTF-8''invoice-INV-2026-001.pdf

<binary content>
```

The endpoint gives `404` when the email does not exist, when the email has no attachment with this file name, or when the original email is no longer in storage.

## Retry a failed email

`POST /api/mailbox/{inbound_email_id}/reprocess` processes a failed email again from the stored original. Use it after you corrected the cause of the failure on your side, or when the failure was temporary (for example a timeout of the PDF conversion). The request has no body.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/mailbox/mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i/reprocess" \
    -H "Authorization: Bearer $E_INVOICE_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const emailId = 'mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i';

  const response = await fetch(`https://api.e-invoice.be/api/mailbox/${emailId}/reprocess`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}` },
  });

  if (response.status !== 202) {
    const error = await response.json();
    throw new Error(`Reprocess refused: ${response.status} ${error.detail}`);
  }

  const email = await response.json();
  console.log(`Reprocessing started for ${email.id}`);
  ```

  ```python Python theme={null}
  import os

  import requests

  email_id = "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i"

  response = requests.post(
      f"https://api.e-invoice.be/api/mailbox/{email_id}/reprocess",
      headers={"Authorization": f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
      timeout=30,
  )

  if response.status_code != 202:
      raise RuntimeError(
          f"Reprocess refused: {response.status_code} {response.json()['detail']}"
      )

  print(f"Reprocessing started for {response.json()['id']}")
  ```

  ```php PHP theme={null}
  <?php
  $emailId = 'mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i';

  $ch = curl_init("https://api.e-invoice.be/api/mailbox/$emailId/reprocess");
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('E_INVOICE_API_KEY')],
  ]);

  $body = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  $data = json_decode($body, true);
  if ($status !== 202) {
      throw new RuntimeException("Reprocess refused: $status " . $data['detail']);
  }

  echo 'Reprocessing started for ' . $data['id'] . "\n";
  ```

  ```csharp C# theme={null}
  using System.Net;
  using System.Net.Http.Headers;
  using System.Text.Json;

  var emailId = "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i";

  using var client = new HttpClient { BaseAddress = new Uri("https://api.e-invoice.be") };
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
      "Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  var response = await client.PostAsync($"/api/mailbox/{emailId}/reprocess", null);
  using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());

  if (response.StatusCode != HttpStatusCode.Accepted)
  {
      throw new InvalidOperationException(
          $"Reprocess refused: {(int)response.StatusCode} {json.RootElement.GetProperty("detail")}");
  }

  Console.WriteLine($"Reprocessing started for {json.RootElement.GetProperty("id")}");
  ```
</CodeGroup>

```json Response (202 Accepted) theme={null}
{
  "id": "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i",
  "message_id": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817",
  "sender_email": "billing@e-invoice.be",
  "sender_name": "E-INVOICE BV",
  "to_addresses": "accounts@openpeppol.org",
  "cc_addresses": null,
  "bcc_addresses": "e-invoice-bv+k3x9q7m2@outbox.email.e-invoice.be",
  "subject": "Invoice INV-2026-001",
  "attachments": [
    {
      "filename": "invoice-INV-2026-001.pdf",
      "size": 184320,
      "content_type": "application/pdf"
    }
  ],
  "attachment_count": 1,
  "processed": false,
  "error_message": null,
  "received_at": "2026-10-01T09:14:22Z",
  "created_at": "2026-10-01T09:14:23Z",
  "processed_at": null,
  "document_id": null
}
```

`202 Accepted` means that the processing started again, not that it is complete. The previous `error_message` is cleared and the email goes back to `pending`. Poll `GET /api/mailbox/{inbound_email_id}` to get the result.

The endpoint refuses the request in these conditions:

| Status code | `detail` | Condition |
| - | - | - |
| `404` | `Inbound email not found` | The ID does not exist or belongs to a different company. |
| `409` | `Email is still pending processing` | The email is `pending`. Wait for the result. |
| `409` | `Email was already processed successfully` | The email is `success`. A second document is not made. |
| `409` | `Email failed but a document was already created — manual intervention required` | The email has an `error_message` and also a `document_id`. Examine the document, or contact support. |
| `422` | `Original email payload is not available in storage — cannot reprocess` | The stored original of the email is gone. Send the email again. |

## Track the result in the document timeline

When an email made a document, the timeline of that document contains two Mailbox events: `email_received` and `email_processed`. The `id` of each event is the ID of the email, and `details` has the subject and the attachment metadata.

```bash cURL theme={null}
curl -X GET "https://api.e-invoice.be/api/documents/doc-4n7c2x9v1b5m8k3j6h0g2f4d7s1a9q5w/timeline" \
  -H "Authorization: Bearer $E_INVOICE_API_KEY"
```

```json Response (extract) theme={null}
{
  "document_id": "doc-4n7c2x9v1b5m8k3j6h0g2f4d7s1a9q5w",
  "events": [
    {
      "event_type": "email_received",
      "timestamp": "2026-10-01T09:14:22Z",
      "id": "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i",
      "details": { "subject": "Invoice INV-2026-001" }
    },
    {
      "event_type": "email_processed",
      "timestamp": "2026-10-01T09:15:41Z",
      "id": "mail-8f3k2j5h7g9d1s4a6q0w2e5r8t1y3u7i",
      "details": { "subject": "Invoice INV-2026-001" }
    }
  ]
}
```

See [Document lifecycle and delivery tracking](/guides/document-lifecycle) for the other events and the document states.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The email is not in the Mailbox">
    Make sure that the full mailbox address, with the part after the `+`, is in the To, CC or BCC field. An email to an address that does not match a company is not stored in a Mailbox. If the company has a list of allowed senders, the From address of the email must be in that list.
  </Accordion>

  <Accordion title="The email failed with an invalid UBL message">
    The UBL attachment does not pass the Peppol BIS Billing 3.0 rules. Use `POST /api/validate/ubl` to get the rule failures, correct the file and send a new email. See [Send UBL documents](/guides/ubl-documents).
  </Accordion>

  <Accordion title="The email failed because no valid e-invoice could be made from the PDF">
    The data extracted from the PDF does not make a valid invoice (for example, a tax ID or a total is missing). Download the attachment to examine it. You can also convert the PDF with `POST /api/documents/pdf`, correct the JSON and create the document with `POST /api/documents/`. See [Create documents from PDF](/guides/pdf-documents).
  </Accordion>

  <Accordion title="The wrong attachment was used">
    When an email has UBL and PDF files, UBL has precedence. When an email has more than one file of the same type, the selection uses the file names. Send one invoice file for each email.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Create documents from PDF" icon="file-pdf" href="/guides/pdf-documents">
    Learn how a PDF is converted into an e-invoice.
  </Card>

  <Card title="Send UBL documents" icon="file-code" href="/guides/ubl-documents">
    Send UBL XML that your system already makes.
  </Card>

  <Card title="Document lifecycle and delivery tracking" icon="arrows-rotate" href="/guides/document-lifecycle">
    Follow a document from draft to delivery.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/essentials/webhooks">
    Get a notification when a document is sent.
  </Card>
</CardGroup>
