# Report Pilot

Source: https://developers.swell.is/app-guides/report-pilot

Report Pilot emails your store's key numbers to the people who need them, on a schedule. Choose any of seven reports (sales, customers, inventory, products, discounts, abandoned carts and subscriptions), how often each one runs, and who gets it. Reports are sent at the hour and in the time zone you choose.

Every report is also saved under **Reporting** in your dashboard, so you can open any past period again. The app reads your store's data and doesn't change it.

## How it works

| When | What the app does |
| --- | --- |
| A report is due: the Send at hour in the app's Time zone, on a day its schedule sets | Builds the report for its period, saves it under Reporting and emails it to its recipients, if Send scheduled reports and the report's own switch are on. |
| A due report has nothing in it | Skips it, without saving or emailing anything, unless Send even when there is nothing to report is on. |
| A due report has no recipients | Saves it under Reporting and emails nobody. |
| A scheduled report fails before it's saved | Tries again in each of the next two hours, stopping at midnight in the app's Time zone. |
| An email fails | Still tries the other addresses, then shows the run as Failed on the Workflows tab. The report stays saved, and the email isn't sent again. |
| You choose Actions > Send a report now | Builds, saves and emails the report you pick right away, even if it's switched off or has nothing in it. Send only to sends it to one address instead of its recipients. |
| You open a report under Reporting | Shows its summary and tables. Long tables show their first rows, with a link to show the rest. |

## Before you begin

- Nothing external. The app needs no other account, API key or connection.
- The email addresses that should get reports.
- Your store's time zone, from **Settings > General**. The app doesn't read it, so you choose it again in the app.

## Set up

Install Report Pilot from the Swell App Store, and set it up in your test environment before the live one.

**1. Choose your reports**

Go to **Apps > Report Pilot > Settings**. Under **Each report**, open each report's tab and turn on the reports you want, such as **Send the inventory report**. The sales report is on from the start. The others are off.

**2. Set the schedules**

On each report's tab, choose how often it runs under **Reporting period**, or **How often** on the **Inventory** tab: daily, weekly, every 2 weeks or monthly. Every report starts as weekly.

**3. Add recipients**

Under **Recipients**, add the addresses that should get every report that's on. To send one report to someone else as well, add them under **Also send this report to** on that report's tab.

With no addresses, reports are saved under **Reporting** and nobody is emailed.

**4. Set the time zone and send time**

Set **Time zone** to the time zone from **Settings > General**, and **Send at** to the hour reports should go out. The defaults are Greenwich Mean Time and 08:00. Click **Save settings**.

**5. Send a test**

Choose **Actions > Send a report now**. Choose a **Report**, enter your own address under **Send only to**, and click **Send**. The dashboard shows "Sending a test of the sales report to name@example.com only. It should arrive within a few minutes. It will be listed under Reporting → Sales reports."

Check that the email arrives, and that the report is listed under **Reporting**. In the test environment, the subject starts with `(TEST)`, such as `(TEST) Sales report: Oct 5 – Oct 11, 2026`.

**6. Go live**

Settings are saved separately for each environment. In the live environment, repeat steps 1 to 5.

The test environment sends real emails. Its scheduled reports and tests go to the addresses you entered, with figures from your test data and `(TEST)` at the start of the subject. To stop the scheduled reports there once you've finished testing, turn off **Send scheduled reports** in the test environment.

## Using Report Pilot

Each report has the same figures on its page and in its email. A percentage change shows a dash when the period before had nothing to compare with.

### Sales

Revenue and orders for the period, compared with the period before. Orders count in the period they were placed.

| Figure | What it counts |
| --- | --- |
| Revenue | Paid, non-canceled orders placed in the period, including shipping and tax, after discounts and refunds. |
| Previous revenue | Revenue in the period before this one. |
| Revenue change | The percentage change from Previous revenue. |
| Orders | Paid, non-canceled orders placed in the period. |
| Previous orders | Orders in the period before this one. |
| Order change | The percentage change from Previous orders. |
| Average order value | Revenue divided by Orders. |
| Items sold | Total units across those orders. |
| New customers | Customers whose first paid order was in the period. |
| Returning customers | Customers who had a paid order before the period started. |
| Subtotal | The value of the items ordered, before discounts, shipping and tax. |
| Discounts | Taken off items by coupons, promotions and other discounts. Shipping discounts aren't included, because Shipping is already after them. |
| Shipping | Shipping charged, after shipping discounts. |
| Tax | Tax charged. |
| Canceled orders | Orders placed in the period that were canceled. Not counted in Revenue. |
| Unpaid orders | Orders placed in the period that aren't paid yet. Not counted in Revenue. Draft orders aren't counted. |
| Refunds | Refunded so far on this period's orders. Already taken out of Revenue. |

On stores whose prices don't include tax, `Subtotal - Discounts + Shipping + Tax - Refunds = Revenue`.

Refunds count against the period the order was placed in, as they stand when the report is built. **Previous revenue** is worked out again for each report, so a later refund can make it lower than the **Revenue** in the last saved report.

**Top products** lists the 10 products that earned the most, with their quantity and revenue. The page shows 5 first, and the email lists 5. Revenue on this list is each item's price times its quantity, before discounts, shipping, tax and refunds, so the list doesn't add up to **Revenue**. To leave it out, turn off **Include top products**.

**Daily breakdown** lists the orders and revenue of each day in the period, including days with no orders. The page and the email show the first 7 days. To leave it out, turn off **Include daily breakdown**.

### Customers

Who bought in the period. Every order in Swell has a customer account, including guest checkouts, so guests are counted.

| Figure | What it counts |
| --- | --- |
| Customers | Different customers with at least one paid, non-canceled order in the period. |
| Previous period | The same count for the period before this one. |
| Change vs previous period | The percentage change in Customers. |
| New customers | Customers whose first paid order was in the period. |
| Returning customers | Customers who had a paid order before the period started. |
| Returning rate | Returning customers as a share of Customers. |
| Repeat buyers | Customers with two or more paid orders in the period. |
| Repeat rate | Repeat buyers as a share of Customers. |
| Average spend per customer | What the period's customers spent, less refunds, divided by Customers. |

**Top customers** lists the 10 customers who spent the most in the period, with their **Orders** and **Amount** in the period, and their **Lifetime amount** and **Lifetime orders**. Amounts are paid orders less refunds. The page shows 5 first, and the email lists 5. On the page, each name opens the customer. To keep customer names out of this report and its email, turn off **Include top customers**.

### Inventory

What's out of stock or running low when the report runs. This report has no period: its schedule sets how often stock is checked, and the report is dated the day it runs.

| Figure | What it counts |
| --- | --- |
| Out of stock | Tracked products and variants with no stock left. |
| Low stock | Tracked products and variants at or below Low stock threshold, but not out. |

**Items** lists every out-of-stock and low item, up to 50: out of stock first, then whatever will run out soonest. Each shows its **Stock**, **Est. days left**, **Sold (30 days)** and **SKU**. The page shows 10 first, and the email lists 10. Past 50, a note says how many more there are.

**Est. days left** is the current stock divided by the average sold per day over the last 30 whole days in the app's time zone, rounded down. It shows a dash for an item that sold nothing in that time.

Only active products with stock tracking on are checked, and a product with variants is checked variant by variant. Inactive and archived variants are left out. With **Low stock threshold** at `0`, only out-of-stock items are listed.

### Products

How each product did in the period. **Item revenue** and the revenue on these lists are each item's price times its quantity, before discounts, shipping, tax and refunds.

| Figure | What it counts |
| --- | --- |
| Products sold | Different products with at least one paid sale in the period. |
| Previous period products sold | The same count for the period before this one. |
| Products with no sales | Active products, created before the period, that sold nothing in it. Shows a dash when List products with no sales is off. |
| Units sold | Total units sold across all products. |
| Item revenue | Revenue from the items sold. |
| Best sellers' share | The share of Item revenue from the best sellers listed, with how many products that is, such as 10 of 37 products. |

The report has four lists. The page shows 5 rows of each first, and the email lists 5.

- **Best sellers:** the 10 products that earned the most, with their quantity, revenue, previous revenue and revenue change.
- **Biggest movers:** the 10 largest changes in revenue, up or down, including products that sold last period and not at all this one.
- **Slowest sellers:** up to 10 products that sold in the period, starting from the least revenue. A product in **Best sellers** isn't repeated, so a store that sold 10 products or fewer sees this list empty.
- **No sales:** active products, created before the period, that sold nothing in it, up to 10, oldest first. Past 10, a note says how many more there are. To leave it out, turn off **List products with no sales**.

### Discounts

How much was given away, and through which coupons and promotions.

| Figure | What it counts |
| --- | --- |
| Discounts given | Taken off by coupons, promotions and other discounts, including shipping discounts. |
| Previous period discounts | Discounts given in the period before this one. |
| Discount change | The percentage change in Discounts given, shown in grey instead of green or red. |
| Orders | All paid, non-canceled orders in the period, with or without a discount. |
| Discounted orders | Orders that had any discount. |
| Discounted orders % | Discounted orders as a share of Orders. |
| Revenue | Revenue from all orders in the period, before refunds. |
| Discounted order revenue | Revenue from the orders that had a discount, before refunds. |
| Discount rate | How much was taken off discounted orders, as a share of what they would have cost at full price. |
| Discounted average order | The average value of orders with a discount. |
| Full-price average order | The average value of orders without a discount. |

**Revenue** on this report is before refunds, so it's higher than the Sales report's **Revenue** when orders were refunded.

**Coupons and promotions** lists the 10 largest, with the **Discount given**, **Orders**, **Order revenue**, **Average discount** and **Type** (Coupon, Promotion or Other). The page shows 5 first, and the email lists 5. Past 10, a note says how many more there are.

Coupons are listed by code, and promotions by name. Discounts that aren't tied to a coupon or promotion are listed as `Shipping discounts` or `Item-level or manual discounts`, so the lines add up to **Discounts given**. An order with a coupon and a promotion counts on both lines, so **Orders** and **Order revenue** can add up to more than the report's totals.

### Abandoned carts

Carts left without checking out, and how many came back. A cart counts in the period it was abandoned, including carts that later became an order. Empty and draft carts are left out.

Swell marks a cart abandoned when it has had no activity for the delay set under **Settings > Notifications** for abandoned carts, from 1 to 24 hours, 3 hours by default.

| Figure | What it counts |
| --- | --- |
| Abandoned carts | Carts abandoned in the period. |
| Abandoned change | The percentage change in Abandoned carts, shown in grey instead of green or red. |
| Still open | Abandoned carts that never became an order, or whose order was canceled. |
| Abandoned value | The total value of those carts when they were abandoned. |
| Average abandoned cart | Abandoned value divided by Abandoned carts. |
| Recovered carts | Abandoned carts that later became an order that wasn't canceled, up to when the report ran. |
| Recovery rate | Recovered carts as a share of Abandoned carts. |
| Recovered revenue | Revenue from those orders that are paid, less refunds. |

**Recovered carts** and **Still open** add up to **Abandoned carts**. A cart is recovered however the customer came back: through Swell's abandoned cart email, on their own, or because staff turned the cart into an order.

**Largest abandoned carts** lists the 10 largest, with the customer, **Items**, **Value**, the day it was **Abandoned** and its **Status** (Recovered or Open). A cart with no customer account shows as `Guest`. Past 10, a note says how many more there are.

**Products left in carts** lists the 10 products with the most value left behind, with the number of **Carts**, the **Quantity** and the **Value**. A cart with two variants of one product counts once for that product. The page shows 5 rows of each list first, and the email lists 5.

### Subscriptions

Recurring revenue and how your subscribers changed. **Active subscriptions**, **On trial**, **Paused** and **Past due** show subscriptions as they are when the report runs. New and canceled subscriptions describe the period.

| Figure | What it counts |
| --- | --- |
| Active subscriptions | Paying subscriptions when the report ran. A subscription set to cancel or pause when its billing period ends still counts until then. |
| Monthly recurring revenue | What active subscriptions bring in per month, leaving out any set to cancel or pause when their billing period ends. |
| On trial | Subscriptions in a free trial when the report ran. |
| New subscriptions | Subscriptions started in the period, including trials. |
| New change | The percentage change in New subscriptions. |
| Net change | New subscriptions minus canceled ones in the period. |
| Canceled | Subscriptions canceled in the period. |
| Canceled change | The percentage change in Canceled, shown in grey instead of green or red. |
| Paused | Subscriptions paused when the report ran. |
| Past due | Subscriptions whose last renewal payment failed: past due while Swell retries, or unpaid once it stops. |
| Renewals in the next 7 days | Active subscriptions due to bill in the 7 days after the period. Any set to cancel or pause aren't counted. |
| Trials ending in the next 7 days | Free trials ending in the 7 days after the period. |

Plans billed daily, weekly or yearly, or every few months, are converted to a monthly amount.

The report has three lists. The page shows 5 rows of each first, and the email lists 5.

- **By plan:** paying and trial subscriptions and their monthly revenue, for up to 10 plans, highest monthly revenue first.
- **Canceled subscriptions:** up to 10 canceled in the period, highest monthly value first, with the customer, plan, date and **Monthly value**. Past 10, a note says how many more there are.
- **Failed renewals:** up to 10, largest amount first, with the customer, plan, **Amount due** and **Status** (Past due or Unpaid). Past 10, a note says how many more there are.

### Schedules and time zones

Each report has its own schedule, set on its tab:

| Schedule | Arrives | Covers |
| --- | --- | --- |
| Daily | Every day | The previous day |
| Weekly | Mondays | The 7 days before, Monday to Sunday |
| Every 2 weeks | Alternate Mondays | The 14 days before |
| Monthly | The 1st of the month | The previous calendar month |

Changes compare with the period before: the previous day, 7 days or 14 days, or the previous calendar month.

Every 2 weeks runs on the same alternate Mondays for every store, such as Nov 9, Nov 23 and Dec 7, 2026, so the first report can take up to 2 weeks to arrive.

**Send at** and **Time zone** apply to every report. Within the **Send at** hour, reports start at these minutes past the hour: Sales 0, Inventory 5, Customers 10, Discounts 15, Products 20, Abandoned carts 25 and Subscriptions 30. Each email arrives within a few minutes. In time zones offset by a half or quarter hour, such as India, reports arrive at other minutes in that hour.

**Time zone** also sets when each day, week and month starts, so a report never includes part of a day. A day that gains or loses an hour to daylight saving is still one day. **Period start** and **Period end** on a report's page show in the local time of the person viewing it.

On a day the clocks change, each report still goes out once. A **Send at** hour that doesn't exist sends at the next hour that does, and an hour that happens twice sends the first time. In New York, for example, a **Send at** of 02:00 sends at 03:00 on the second Sunday of March, and a **Send at** of 01:00 sends once on the first Sunday of November.

A scheduled report that fails before it's saved is tried again in each of the next two hours. Retries stop at midnight in the app's **Time zone**, so a **Send at** of 22:00 gets one retry and 23:00 gets none. See Troubleshooting below.

### Recipients

**Recipients** get every report that's on. **Also send this report to**, on each report's tab, gets only that report, as well as **Recipients**.

- An address on both lists gets one email.
- Anything that isn't an email address is ignored.
- Each address gets its own email, with your store's name as the sender.
- Store admins aren't emailed unless they're on a list.

Each email has the same summary and tables as the report's page, with these differences:

- There are no links to products or customers.
- Long lists show their first rows, then a line such as "5 more on this report in your dashboard" with a link to the saved report. The link needs a login to your Swell dashboard.
- Empty or switched-off lists are left out, except **Items** on the Inventory report, which says when nothing is out of stock or running low.

Reports show customers by name, never by email address. A customer with no name shows as `Unnamed customer`. The Customers, Abandoned carts and Subscriptions reports all list names. **Include top customers** keeps them out of the Customers report, and the other two have no such setting.

To change an email's **Intro line** or **Footer note**, go to **Settings > Notifications** and open the report in the **Admin** section, such as **Sales report**. The **Email subject**, **From email** and **BCC emails** are there too.

### Reports under Reporting

To open a saved report, go to **Reporting** in the left menu and choose a list, such as **Sales reports** or **Abandoned cart reports**. Each row is one saved report, newest first, and **Generated** shows when it was built. Click a row to open it.

A report's page shows its summary, then its tables. To see what a figure counts, hover over it and use its info icon. **View product** opens the product in the dashboard, and customer names open the customer. Long tables show their first rows, with a link to show the rest.

**Sales reports** has **With sales** and **No sales** tabs. **No sales** lists reports with no orders, which are saved only when **Send even when there is nothing to report** is on or you send one with **Send a report now**.

A saved report doesn't change, and sending the same period again saves a second report. The app never removes saved reports.

### Send a report now

To send a report outside its schedule, go to **Apps > Report Pilot**, choose **Actions > Send a report now**, choose a **Report**, and click **Send**.

- With **Send only to** empty, the report goes to its recipients. With one address, it goes only to that address.
- It sends even if the report or **Send scheduled reports** is off, and even if the report has nothing in it.
- It covers the most recent full period of the report's schedule: the previous day, the 7 or 14 days before today, or the previous calendar month. On any day but Monday, a weekly report covers a different week from the scheduled one. The Inventory report shows stock as it is when it runs.
- The dialog closes as soon as the report has started, and the report is built in the background, so the dialog can't show a problem with the report itself. The message says how many recipients it's going to, such as "Sending the sales report to 2 recipients. It should arrive within a few minutes. It will be listed under Reporting → Sales reports."
- Runs are listed on the app's **Workflows** tab as **Send report**, with their status: Running, Completed, Failed or Canceled. A run shows Failed when the report wasn't saved or an email failed. **Started by** shows App function. Click a run to see its steps.

### Empty reports

A scheduled report with nothing in it is skipped. It isn't saved or emailed. Because nothing was saved, the app builds it again in each of the next two hours, so the **Workflows** tab shows a run for each try. A report that has something in it by then is saved and emailed.

| Report | Skipped when |
| --- | --- |
| Sales | The period had no paid orders. |
| Customers | No customer placed a paid order in the period. |
| Inventory | Nothing is out of stock or low. |
| Products | No products sold in the period. |
| Discounts | The period had no discounted orders. |
| Abandoned carts | No carts were abandoned in the period. |
| Subscriptions | Nothing is active, on trial, paused or past due, and none were started or canceled in the period. |

To save and email these reports anyway, turn on **Send even when there is nothing to report**. It applies to every report. **Send a report now** always sends.

### In the dashboard

| Where | What it's for |
| --- | --- |
| Apps > Report Pilot > Settings | The switches, recipients, Time zone and Send at, and a tab for each report. |
| Apps > Report Pilot > Actions | Send a report now. Save the settings first: the action is unavailable while there are unsaved changes. |
| Apps > Report Pilot > Workflows | The runs that build, save and email reports, with their status, when they started, and how long they took. |
| Reporting | Sales reports, Customer reports, Inventory reports, Product reports, Discount reports, Abandoned cart reports and Subscription reports. |
| Settings > Notifications | Each report's email, in the Admin section: Intro line, Footer note, subject and sender. |
| Developer > Console > Logs | Each report the app builds, skips or sends, and each error. |

## Settings

Settings are in **Apps > Report Pilot > Settings** and are saved separately for each environment.

**Send scheduled reports**, **Send even when there is nothing to report**, **Recipients**, **Time zone** and **Send at** are at the top. Below them, under **Each report**, each report has a tab: **Sales**, **Customers**, **Inventory**, **Products**, **Discounts**, **Abandoned carts** and **Subscriptions**. Field descriptions show as tooltips.

| Setting | Description | Default |
| --- | --- | --- |
| Send scheduled reports | Turns every scheduled report on or off without uninstalling the app. While it's off, scheduled reports aren't saved or emailed. Send a report now still works. | On |
| Send even when there is nothing to report | Saves and emails a report whose period had nothing in it. See Empty reports above. | Off |
| Recipients | Addresses that get every report that's on. | None |
| Time zone | The time zone for the Send at time and for where each day, week and month starts. Uses the same list as Settings > General. | Greenwich Mean Time |
| Send at | The hour reports are sent, in the chosen time zone, from 00:00 to 23:00. | 08:00 |
| Sales > Send the sales report | Switches the sales report on or off. | On |
| Sales > Reporting period | How often the report runs and what it covers: Daily (previous day), Weekly (last 7 days), Every 2 weeks (last 14 days) or Monthly (previous calendar month). | Weekly (last 7 days) |
| Sales > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Sales > Include top products | Adds the Top products list to the report and email. | On |
| Sales > Include daily breakdown | Adds the day-by-day table to the report and email. | On |
| Customers > Send the customer report | Switches the customer report on or off. | Off |
| Customers > Reporting period | As on the Sales tab. | Weekly (last 7 days) |
| Customers > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Customers > Include top customers | Adds the Top customers list. While it's off, customer names stay out of the report and email. | On |
| Inventory > Send the inventory report | Switches the inventory report on or off. | Off |
| Inventory > How often | How often stock is checked: Daily, Weekly (Mondays), Every 2 weeks (alternate Mondays) or Monthly (the 1st). | Weekly (Mondays) |
| Inventory > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Inventory > Low stock threshold | Stock-tracked products and variants at or below this level are listed as low, from 0 to 1,000. | 5 |
| Products > Send the product report | Switches the product report on or off. | Off |
| Products > Reporting period | As on the Sales tab. | Weekly (last 7 days) |
| Products > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Products > List products with no sales | Adds the No sales list. While it's off, Products with no sales shows a dash. | On |
| Discounts > Send the discount report | Switches the discount report on or off. | Off |
| Discounts > Reporting period | As on the Sales tab. | Weekly (last 7 days) |
| Discounts > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Abandoned carts > Send the abandoned cart report | Switches the abandoned cart report on or off. | Off |
| Abandoned carts > Reporting period | As on the Sales tab. | Weekly (last 7 days) |
| Abandoned carts > Also send this report to | Addresses that get only this report, as well as Recipients. | None |
| Subscriptions > Send the subscription report | Switches the subscription report on or off. | Off |
| Subscriptions > Reporting period | As on the Sales tab. | Weekly (last 7 days) |
| Subscriptions > Also send this report to | Addresses that get only this report, as well as Recipients. | None |

## Limitations

- Each report reads a fixed amount of data: 2,500 orders for top products, the Products report and the Coupons and promotions list; 20,000 customers for customer counts and the new and returning split; 2,000 products for the Inventory report and the No sales list, plus 500 variants per 100 products and 2,500 orders for the last 30 days' sales; 2,500 carts; and 2,500 running, 2,500 past due and 2,500 canceled subscriptions. Revenue, order counts and Discounts given have no limit. When a report reaches a limit, its page and email say the figures are partial.
- The app has its own **Time zone**. If it differs from **Settings > General**, reports follow the app's.
- **Send at** and **Time zone** apply to every report. Weekly and every-2-weeks reports always arrive on Mondays, and monthly reports on the 1st.
- A scheduled report that still isn't saved two hours after **Send at** isn't tried again later, and failed emails aren't sent again.
- A report sent with **Send a report now** after the **Send at** hour starts counts as saved, even one sent with **Send only to**. If the scheduled run failed, it isn't tried again, and the report's recipients don't get it.
- The app keeps no other record of what it has sent. Changing **Send at** or **Time zone** can send a report twice or skip it that day, and turning a report on in the two hours after **Send at**, on a day it's due, sends it at its next hourly run.
- A saved report doesn't change. An order that's paid, canceled or refunded after its report was built isn't updated in that report and doesn't move to a later one, because orders count in the period they were placed.
- On stores that sell in more than one currency, amounts are added up without converting them, so treat totals as approximate. A report uses the currency of the most recent paid order in the period, then of the store's most recent order, then USD. The Subscriptions report uses the subscriptions' currency.
- Plans billed daily, weekly, monthly or yearly are converted to a monthly amount. Any other interval counts as monthly.
- Links in emails open your Swell dashboard, so a recipient without a login can read the email but can't open the report.
- The test and live environments have separate settings and reports, and both send real emails.

## Troubleshooting

**Send a report now** shows its messages in the dashboard, and each report it builds has a run on the app's **Workflows** tab. The app also logs each report it builds, skips or sends. In the Swell dashboard, the logs are under **Developer > Console > Logs**. To see only this app's lines, choose **Report Pilot** in the **App** filter.

### A report didn't arrive

Check that **Send scheduled reports** and the report's own switch are on, that today is a day its schedule sends, and that the report has recipients. Then choose **Actions > Send a report now** to check that the email arrives, and look in the logs for the report's last line:

- "Sales report skipped: turned off in app settings." **Send scheduled reports** or the report's own switch is off.
- "Sales report skipped: weekly report is not due today." Today isn't a day its schedule sends.
- "Sales report skipped: nothing to report for …" The period was empty. See Empty reports above.
- "Sales report saved for … No recipients are set." Add addresses under **Recipients** or **Also send this report to**.
- "Sales report created for … Sent to 1 recipient(s)" Swell accepted the email. Check the recipient's spam folder.

### "… failed: …"

A run shows Failed when any email fails. The other addresses are still tried, the report stays saved, and the failed email isn't sent again. The log line ends with the failures, such as "Sent to 1 recipient(s), 1 failed: …: Notification is not enabled", with the address shortened.

- "Notification is not enabled": the report's email is turned off under **Settings > Notifications**. Turn it on.
- "Notification template not found": Swell couldn't find the report's email. Email support@swell.is.

To send a failed email again, fix the cause and choose **Actions > Send a report now** on the same day. With **Send only to**, it goes only to that address.

Each send is checked once, about 5 seconds after it's created. If an email doesn't arrive and the logs show no failure, check the address, and choose **Actions > Send a report now** to try again.

### A run shows Failed on the Workflows tab

Open the run to see its **Error** and the step that failed. A message such as "Swell refused GET /orders: …" names the call Swell refused, and "Sales report was not saved: …" means the save failed. In both cases the report wasn't saved, so nothing was emailed. The build step is tried up to 3 times within a run, and the saving step isn't tried again.

An **Error** such as "Sales report for Oct 5 – Oct 11, 2026: 1 of 2 email(s) failed." means the report was saved and at least one email failed. Every step shows as finished. See "… failed: …" above.

A scheduled report that failed before it was saved is tried again in a new run in each of the next two hours, so a later run can show Completed. Retries stop at midnight in the app's **Time zone**. **Send a report now** runs aren't tried again.

A report that's still missing after that isn't sent later. To send it, choose **Actions > Send a report now** on the same day, so it covers the same period.

### "… could not be started, so nothing was sent. Try again in a minute."

**Send a report now** couldn't start the report. The message starts with the report's name. Wait a minute and try again.

### "Enter one email address in Send only to, or leave it empty."

**Send only to** holds something that isn't one email address. Enter one address, or clear the field.

### "Choose a report type to send."

**Report** is empty. Choose a report and click **Send**.

### "Creating the … now. No recipients are set in the app settings, so no email will be sent. …"

The report is saved under **Reporting**, but it has no recipients. Add addresses under **Recipients** or **Also send this report to**, click **Save settings**, and send it again.

### Send a report now is unavailable

The settings have unsaved changes, and the menu shows "Save your settings before running this action". Click **Save settings**, and try again.

### The report covers the wrong days or arrives at the wrong hour

Check that **Time zone** in the app's settings matches **Settings > General**, and check **Send at**. The default is Greenwich Mean Time, not your store's time zone. On a day that isn't a Monday, **Send a report now** covers a different week from the scheduled report.

A report that arrives an hour or two after **Send at** was tried again after a failed run. On a day the clocks change, the **Send at** hour can move. See Schedules and time zones above.

### A report arrived twice

**Send at** or **Time zone** was changed after the report was sent that day. See Limitations above. Both copies are listed under **Reporting**.

### A period is missing under Reporting

The report was skipped because it had nothing in it, or it was switched off when it was due. See Empty reports above. To build a report for the most recent period, choose **Actions > Send a report now**.

### Figures don't match between reports

**Revenue** on the Sales report is after refunds. **Revenue** on the Discounts report is before refunds. **Item revenue** on the Products report and the revenue on product lists are before discounts, shipping, tax and refunds.

### "… than the report reads, …"

The report reached one of the limits under Limitations above, and its page and email say which figures are partial, such as "This store has more orders or products than the report reads, so some figures and lists cover only part of the period." For the Sales, Customers, Products, Discounts and Abandoned carts reports, a shorter schedule, such as weekly instead of monthly, keeps each report within the limits. The Inventory and Subscriptions reports read every product or subscription whatever the schedule, so a shorter schedule doesn't change them.

On the Discounts report, the note covers only the **Coupons and promotions** list. The figures above it are complete.

### The subject starts with (TEST)

The email came from your test environment, and its figures are from your test data. To stop scheduled reports there, turn off **Send scheduled reports** in the test environment.

## Uninstalling

To uninstall the app, choose **Actions > Uninstall app**. Scheduled reports and their emails stop. Uninstalling removes the app's functions, including the seven schedules, its settings, including the recipient lists, the report lists under **Reporting**, and the seven notifications. Uninstalling applies to the environment you are in, so remove the app from the test and live environments separately.

Saved reports aren't deleted. You can't open them while the app is uninstalled, because their lists and pages are removed. They come back if you reinstall the app in the same environment, and the settings start again from their defaults.

To stop scheduled reports without uninstalling the app, turn off **Send scheduled reports**.

## For developers

| Function | Runs on | What it does |
| --- | --- | --- |
| sales-report | Cron, every hour at minute 0 | Starts send-report for the sales report when it's on, due today, and in the Send at hour, or in either of the two hours after it if no report was saved. |
| inventory-report | Cron, every hour at minute 5 | The same for the inventory report. |
| customer-report | Cron, every hour at minute 10 | The same for the customer report. |
| discount-report | Cron, every hour at minute 15 | The same for the discount report. |
| product-report | Cron, every hour at minute 20 | The same for the product report. |
| cart-report | Cron, every hour at minute 25 | The same for the abandoned cart report. |
| subscription-report | Cron, every hour at minute 30 | The same for the subscription report. |
| send-report | Started by the crons, send-report-now and run-report | A workflow. Builds, saves and emails one report. |
| send-report-now | The Send a report now action | Validates the report type and address, starts send-report with force true, and returns the message. |
| run-report | Route, GET and POST, not public | GET builds a report and returns it without saving or sending. POST starts send-report if the report is on. |
| dashboard-path | Route, GET, not public | Returns the dashboard address for links in report tables. Answers only app admin components with a store user, and returns 403 otherwise. |

Each cron runs every hour, at its own minute in UTC. `runScheduled` checks the switches, then `sendHourRun` and `isDueToday`, all in the `timezone` setting. Weekly is due on Mondays, monthly on the 1st, and `biweekly` on Mondays in even weeks counted from Monday, Jan 5, 1970. `sendHourRun` counts whole hours from `sendHourStart`, the first instant of the local day at or past the `send_hour` setting. It returns `0` for that hour, `1` or `2` for the two hours after it (`SEND_RETRY_HOURS`), and `null` for any other run, including once the local day ends. Run `0` always starts the workflow. Runs `1` and `2` start it only if no report of that type has a `date_created` at or after `sendHourStart`, so a run that failed before saving is retried and an hour that happens twice sends once. A refused lookup skips the retry. The crons keep no other record of past sends. A cron has a 10-second timeout, so it looks up the notification's template and starts the workflow, then returns.

The workflow's run data holds `type`, `now`, `source` (`schedule`, `action` or `route`), and optional `period`, `force`, `only_to`, `dashboard` and `template`. Workflow requests carry only `store.id` and can't read `/:models`, so the function that starts a run passes the dashboard address and the template. A public key with `_test_` in it means the test environment, and its links use `/admin/test`. The steps are:

- `build report`: reads the settings and builds the report, with 2 retries from 15 seconds with exponential backoff, and a 5-minute timeout.
- A run stops here, without saving, when the report is empty, `force` isn't set and `send_when_empty` is off. Nothing was saved, so runs `1` and `2` of a scheduled report start the workflow again.
- `save report`: posts the report to its collection, with no retries and a 2-minute timeout.
- `email recipient 1`, `email recipient 2` and so on: one step per address, with no retries and a 1-minute timeout. A failed address is recorded, and the rest still go out.
- `wait for delivery` and `check delivery`: after 5 seconds, reads each notification back and records its `error`.
- After the last step, a run with any failed address throws `{label} for {period}: {failed} of {total} email(s) failed.`, so it ends as Failed. The report is saved, so runs `1` and `2` don't send it again.

A step that finished isn't run again when a later one fails. In a workflow, a refused Swell call resolves with `{ errors }` instead of throwing, so `failOnRefusal` wraps the client and throws `Swell refused {METHOD} {path}: {reason}`, and a refused read fails the build step instead of producing a report of zeros.

Reports are stored in seven app collections: `reports` (sales), `inventoryreports`, `customerreports`, `productreports`, `discountreports`, `cartreports` and `subscriptionreports`. Each has a list view with `nav.parent` set to `reporting`, and a record view built from the `ReportSummary` and `ReportTable` components. `SettingsSection` draws the **Each report** heading on the settings page.

Each collection has one notification: `sales-report`, `inventory-report`, `customer-report`, `product-report`, `discount-report`, `cart-report` and `subscription-report`. Their conditions never match (`id` must not exist), so saving a report emails nobody by itself. Each email is a `POST /notifications` with `template`, `record_id` and `to`. The template is `app_<app id>.apps_<app id>_<collection>.<name>`, with the app ID read from `GET /:models`. A `to` keeps these admin notifications from going to the store's admin users. Addresses at `example.com`, `example.net`, `example.org`, or under `.test`, `.example` or `.invalid` are rendered but never delivered.

Settings arrive nested under `report-config` in `swell.settings()`. Unset values use the code's defaults, and a time zone the runtime doesn't know falls back to UTC.

The app's permissions are `read_orders`, `read_products`, `read_accounts`, `read_carts`, `read_subscriptions`, `read_promotions` (for promotion names on the discount report), `write_notifications` (to email recipients) and `read_:models` (to find the notification templates). It writes only to its own collections and `/notifications`.

To preview a report without saving or sending it:

```sh
swell api get '/functions/report_pilot/run-report'
swell api get '/functions/report_pilot/run-report?type=carts&period=monthly'
swell api get '/functions/report_pilot/run-report?days=90'
```

`type` is `sales` (the default), `inventory`, `customers`, `discounts`, `products`, `carts` or `subscriptions`. `period` is `daily`, `weekly`, `biweekly` or `monthly`. `days` is any number of days up to 730, for the sales report only, and a window over 31 days has no daily breakdown. `swell api post` on the same path starts `send-report` with the same `type` and `period`, returns right away, and honors the report's switch and the empty-report setting.

## Support

Swell builds and maintains the Report Pilot app. For help with the app, email support@swell.is.
