Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Practice and contributor guides explain how to make and verify changes:
- [Feature flags](practices/feature-flags.md)
- [Internationalization](practices/internationalization.md)
- [Performance testing](contributing/performance-testing.md)
- [Stripe testing](contributing/testing-stripe.md)
- [Testing development URLs and devices](contributing/testing-development-urls.md)

Reference guides provide tables and other information to look up while working
Expand Down
3 changes: 3 additions & 0 deletions docs/codebase/stripe-flows.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ This flow is implemented in

The checkout and tier price flows are implemented by
[`payments-service.js`](../../ghost/core/core/server/services/members/members-api/services/payments-service.js).

For manual and automated development workflows, see
[Testing Stripe locally](../contributing/testing-stripe.md).
38 changes: 4 additions & 34 deletions docs/contributing/development-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ environment and adds the listed tooling:
| `pnpm dev:analytics` | Tinybird-backed analytics with the latest published version of the Traffic Analytics service |
| `pnpm dev:analytics:local` | Tinybird-backed analytics with your locally running instance of the Traffic Analytics service |
| `pnpm dev:storage` | S3-compatible storage through MinIO on ports `9000` and `9001` |
| `pnpm dev:stripe` | Stripe webhooks exactly as production receives them; requires Tailscale, see below |
| `pnpm dev:stripe` | Stripe webhooks exactly as production receives them; see [Stripe testing](testing-stripe.md) |
| `pnpm dev:mailgun` | Mailgun API delivery; see [email testing](testing-email.md) |
| `pnpm dev:full` | Public app watchers plus analytics, storage, and Stripe |

Copy [`.env.example`](../../.env.example) to `.env` only when you need an
Expand All @@ -123,38 +124,6 @@ To open Ghost on a phone or another computer, or to exercise HTTPS,
subdirectory, and separate-Admin URL behaviour, see
[Testing development URLs and devices](testing-development-urls.md).

### Stripe webhooks

`pnpm dev:stripe` runs the webhook path production runs. It publishes Ghost's
webhook route, and nothing else, through
[Tailscale Funnel](https://tailscale.com/kb/1223/funnel), and Ghost registers a
pinned webhook endpoint at that address once Stripe is connected in Admin, then
deletes it on shutdown.
The site and Admin stay on `localhost`, so hot reload and the rest of the
development environment work as usual. Use it when the shape of a webhook
payload matters, for example when reading new fields from a checkout session.
Ghost logs an error whenever an event arrives rendered at a different API
version from the one it pins, in any environment.

The webhook route is reachable from the internet while the command runs; every
request to it must carry a valid Stripe signature. The tunnel is a child
process of the command and ends with it, including on Ctrl-C. Only a forced
kill of the command can leave the tunnel running, and even then it does not
survive a restart of Tailscale or the machine.

Funnel needs Tailscale 1.52 or newer with MagicDNS, HTTPS certificates and
Funnel enabled for your tailnet and node. The command reports when Tailscale is
missing, not signed in, or has no MagicDNS name; for the other requirements it
shows Tailscale's own error.

`pnpm dev:stripe --listen` forwards events with `stripe listen` instead, which
needs `STRIPE_SECRET_KEY` in the environment or a local `.env` file but no
Tailscale. The CLI renders every event at your Stripe account's default API
version, which cannot be pinned, so an event can carry a different shape from
the one production receives; the command warns about this at startup and Ghost
logs an error when a mismatched event arrives. Use it only when the payload
shape does not matter.

## Data and email

After creating the local owner account, populate a development site with stable
Expand All @@ -177,7 +146,8 @@ pnpm migrate:db
```

Development email is captured by Mailpit rather than delivered. Open
[http://localhost:8025](http://localhost:8025) to inspect messages.
[http://localhost:8025](http://localhost:8025) to inspect messages. For Mailgun
delivery and automated-test workflows, see [Email testing](testing-email.md).

## Updating and recovering

Expand Down
56 changes: 46 additions & 10 deletions docs/contributing/testing-email.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,61 @@
# Receiving and Testing Emails

## Local email
## Use Mailpit by default

The normal development environment starts Mailpit with Ghost. Run:

```bash
pnpm dev
```

Emails sent by the development site are captured at
Transactional emails sent by the development site are captured at
[http://localhost:8025](http://localhost:8025) rather than delivered. The Docker
development configuration connects Ghost to Mailpit automatically.

## Testing with Mailgun
Use Mailpit for ordinary local work. It is quick, keeps test messages on your
machine, and does not require provider credentials. It does not exercise the
Mailgun API used for newsletters and other bulk email.

For testing transactional email delivery, configure Ghost's `mail` setting with
an SMTP provider. For testing newsletter delivery, configure the separate
Mailgun settings used by Ghost's bulk email service. Mailgun sandbox domains
only send to recipients that have been added and verified in Mailgun.
## Test with Mailgun

Keep credentials in your local configuration and do not commit them.
Use the Mailgun development variant when the provider interaction is part of
the behaviour you need to test. It routes transactional, newsletter, and
automation email through the Mailgun API:

Most development does not need real delivery. Use Mailpit unless the behavior
being tested depends on the external provider.
```bash
pnpm dev:mailgun
```

Copy [`.env.example`](../../.env.example) to `.env` and provide a test Mailgun
domain and API key. The example also documents the optional sender and the
different API URLs required by EU domains. Never commit `.env` or provider
credentials.

The development variant supplies Ghost's Mailgun configuration for you. Do not
add SMTP credentials or Mailgun settings to `config.local.json`.

If you use a Mailgun sandbox domain, add and verify each intended recipient in
Mailgun before testing delivery.

This sends real email through an external service. Use test addresses and the
smallest useful recipient list. Return to `pnpm dev` when provider behaviour is
not under test.

## Automated tests

Automated tests must not call the real Mailgun API. Browser E2E tests can enable
the suite's fake Mailgun service:

```ts
test.use({mailgunEnabled: true});
```

The fake service records Mailgun requests and forwards rendered messages to
Mailpit, where tests can inspect them with the existing email fixture. See the
[E2E workspace README](../../e2e/README.md) and the
[newsletter-send test](../../e2e/tests/admin/posts/newsletter-send.test.ts) for
the current fixtures and an example.

Ghost Core tests should use the existing Mailgun stubs and email test utilities
instead of provider credentials. Start with the [testing guide](testing.md) to
choose the suite closest to the behaviour being changed.
85 changes: 85 additions & 0 deletions docs/contributing/testing-stripe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Testing Stripe Locally

Use a Stripe test-mode account and Stripe's test payment details for all local
development. Never use live keys or real payment details.

## Receive production-shaped webhooks

Run:

```bash
pnpm dev:stripe
```

This follows the production webhook path. It publishes only Ghost's Stripe
webhook route through [Tailscale Funnel](https://tailscale.com/kb/1223/funnel).
Once Stripe is connected in Admin, Ghost registers a temporary webhook endpoint
using its pinned Stripe API version and removes it on shutdown. The site and
Admin remain on `localhost`.

Use this mode when webhook payload shape matters, such as when reading fields
from a checkout session. Ghost logs an error if an event arrives at a different
API version from the one it pins.

Funnel requires Tailscale 1.52 or newer, with MagicDNS, HTTPS certificates, and
Funnel enabled for the tailnet and node. The command reports when Tailscale is
missing, disconnected, or has no MagicDNS name. Other setup failures include
Tailscale's own error.

The webhook route is publicly reachable while the command runs, but every
request must have a valid Stripe signature. The tunnel closes when the command
stops. A forced kill can leave it running until Tailscale or the machine
restarts. Turn it off manually if this happens:

```bash
tailscale funnel --https=443 off
```

## Use the Stripe CLI fallback

When Tailscale is unavailable and exact webhook payload shape does not matter,
run:

```bash
pnpm dev:stripe --listen
```

This uses `stripe listen` in Docker and requires `STRIPE_SECRET_KEY` in the
environment or a local `.env` file. The key must be a test-mode key for the same
Stripe account connected to Ghost. The command does not require a local Stripe
CLI installation or `stripe login`. Never commit `.env` or Stripe credentials.

Stripe CLI renders events at the account's default API version rather than the
version Ghost pins. The command warns about this difference, and Ghost logs an
error when it receives a mismatched event.

## Test a paid membership

1. Start `pnpm dev:stripe`.
2. Connect a Stripe test-mode account in Ghost Admin under
**Settings → Tiers**. Ghost registers the temporary webhook endpoint when the
connection settings are saved. Follow the development log's instruction to
restart if registration could not happen during the first connection.
3. Sign up for a paid membership through the local site's Portal using a
[Stripe test card](https://docs.stripe.com/testing), such as
`4242 4242 4242 4242` with any future expiry date and any three-digit CVC.
4. Confirm that the member becomes paid in Admin. This verifies that Ghost
received and processed the webhook.

## Automated tests

Automated browser tests must use the E2E suite's fake Stripe service rather than
a real account:

```ts
test.use({stripeEnabled: true});
```

This gives the test an isolated Ghost environment, fake Checkout page, Stripe
test service, and signed webhook delivery. See the
[E2E Stripe fixture guide](../../e2e/README.md#stripe-fixtures) and the
[subscription lifecycle test](../../e2e/tests/public/stripe-webhook-subscription-lifecycle.test.ts)
for the current helpers and an example.

For the implementation behind Stripe Connect, tier creation, and subscription
checkout, see [Stripe flows](../codebase/stripe-flows.md).
3 changes: 3 additions & 0 deletions docs/contributing/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ For physical-device testing and URL configurations such as HTTPS,
subdirectories, or a separate Admin origin, see
[Testing development URLs and devices](testing-development-urls.md).

For provider-backed development and the test doubles used by browser tests, see
[Email testing](testing-email.md) and [Stripe testing](testing-stripe.md).

## Run Focused Tests

Nx can run a target for one workspace from the repository root:
Expand Down
Loading