> ## Documentation Index
> Fetch the complete documentation index at: https://playbook.smedleygroup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GKL Platform: System Architecture

> The three Lambdas, the two API gateways, and how the platform fits together end to end.

This page describes how GKL Platform is put together.

## Architecture

![GKL Platform architecture. One API Lambda serves both web apps and every Vivenu webhook, with two worker Lambdas behind SNS and SQS. Colour shows the kind of component.](https://mintcdn.com/smedley_group/J4Qb1kGrTQEJOjVR/images/software/developer/estate/systems/gkl/architecture.png?fit=max\&auto=format\&n=J4Qb1kGrTQEJOjVR\&q=85\&s=bddeefd623edbf75782f65578d185a6a)

> [!NOTE]
>
> The diagram source is the draw\.io file at `images/software/developer/estate/systems/gkl/architecture.drawio`. To change the diagram, edit that source in draw\.io, re-export it as PNG to `architecture.png` in the same folder, and commit both.

## Three Lambdas

| Lambda | Runtime | Triggered by | Does |
| - | - | - | - |
| `GklPlatform-APIGtwLambda` | .NET 8 | API Gateway | Everything. Eligibility, checkout, webhook ingest, admin operations |
| `GklPlatform-NotificationService` | Python 3.12 | `GklPlatformUserNotificationQueue` | Renders Jinja email templates and sends through SES |
| `GklPlatform-HubSpotService` | Python 3.12 | `GklPlatformHubspotQueue` | Pushes contacts into HubSpot |

The API Lambda is a normal ASP.NET Core application wrapped for Lambda, which is why the same code runs locally as an ordinary web API. It holds all the business logic; the other two exist only to take slow work off the request path.

## Why The Work Is Split

An email send or a CRM call is slow, can fail, and must not make a customer wait or a Vivenu webhook time out. So the API Lambda does not do either itself. It publishes to an SNS topic and returns.

Each topic feeds an SQS queue, and each queue triggers a worker Lambda. That gives retries and a visibility timeout without the API knowing anything about SES or HubSpot.

| Chain | Carries |
| - | - |
| `UserNotificationTopic` → queue → NotificationService | Registration, email confirmation, forgotten and changed passwords, user invitations |
| `HubspotTopic` → queue → HubSpotService | New user registrations, for the CRM |

Messages are routed by SNS **subject**, which is set per environment, so the worker inspects the subject to decide what it is dealing with.

> [!WARNING]
>
> Neither queue has a dead letter queue. A message that fails repeatedly is retried until it expires and is then discarded with no record. Queue depth is the only signal that anything went wrong.

## Why The Notification Lambda Calls Back

The notification worker does not need to read the database. Everything it needs to render an email arrives in the SNS message.

What it cannot do is sign a token. Password reset and email confirmation links carry a JWT with a special claim (`RESET_PASSWORD` or `VERIFY_EMAIL`), and only the API holds the signing key. So the worker posts to the API's `jwt` endpoint, gets a token back, and builds the link itself.

That is the reason the private gateway exists. It is restricted by resource policy to a VPC endpoint, so this internal hop never crosses the public internet, and it is authenticated with an internal API key separate from the one the web apps carry.

## The Two API Gateways

| Gateway | Type | Used by |
| - | - | - |
| Public | REGIONAL, behind WAF | Booking website, Operator Dashboard, Vivenu webhooks, the telemetry converter |
| Private | VPC endpoint only | The notification Lambda calling back in |

The WAF is associated with the **public gateway's stage**, not placed in front of any one caller, so everything reaching the public gateway passes through it: both web apps, Vivenu's webhooks and the telemetry converter. It applies a rate limit and the AWS managed rule sets. The private gateway has no WAF.

## What Sits Around It

**MongoDB** holds everything, on the shared EC2 instance provisioned by the infrastructure repository rather than by this one. See [The Domain Model](/software/developer/estate/systems/gkl/domain-model).

**Secrets Manager** holds the internal and external API keys, which the API Lambda reads at runtime.

**An SSM parameter** publishes the public API URL, which is how the two web app repositories and the telemetry repository discover where the API lives. See [Deployment and Environments](/software/developer/estate/systems/gkl/deployment).

**Five CloudWatch alarms** across the three Lambdas and the WAF deliver to an SNS topic that emails the team.

## Notes

* All three Lambdas share one IAM role, and it is broader than any of them needs.
* The API Lambda runs in the VPC because MongoDB is only reachable from inside it.

## Related Documentation

| Page | Relevance |
| - | - |
| [How A Sale Happens](/software/developer/estate/systems/gkl/how-a-sale-happens) | The revenue path through this architecture |
| [The Domain Model](/software/developer/estate/systems/gkl/domain-model) | What is stored and how it relates |
| [Deployment and Environments](/software/developer/estate/systems/gkl/deployment) | How all of this is built and released |
| [TalentID Coaching: System Architecture](/software/developer/estate/systems/talentid/architecture) | The system on the other side of the Operator Dashboard |


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