diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..5f9883604d6c33d89c24750247644fc373b82fc5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root=true
+
+[*.{yml,yaml}]
+indent_style=space
+indent_size=2
+
+[*.md]
+indent_style=space
+indent_size=2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8ca9a958d732525663084ba0449dcbe73792e025
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# mypy
+.mypy_cache/
+
+# buf
+gen
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..10c655f57e4492a6a9877054f96d5c616430594b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,10 @@
+include:
+  - project: "uis/devops/continuous-delivery/ci-templates"
+    file: "/auto-devops/common-pipeline.yml"
+    ref: v6.7.2
+
+variables:
+  BUILD_DISABLED: "1"
+  PYTHON_BUILD_DIST_DISABLED: "1"
+  CONTAINER_SCANNING_DISABLED: "1"
+  DAST_DISABLED: "1"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a7a2df0069b6d1a3a4cc03574b177ce1952b4586
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,34 @@
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.4.0
+    hooks:
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+        # TODO: Remove this once
+        # https://gitlab.developers.cam.ac.uk/uis/devops/continuous-delivery/ci-templates/-/issues/57
+        # is addressed.
+        exclude: ^(CHANGELOG\.md|\.copier-answers\.yml)$
+      - id: check-yaml
+        args:
+          - --unsafe
+      - id: check-json
+      - id: check-toml
+      - id: check-xml
+      - id: check-added-large-files
+      - id: check-executables-have-shebangs
+      - id: check-merge-conflict
+      - id: check-symlinks
+      - id: detect-private-key
+      - id: mixed-line-ending
+      - id: pretty-format-json
+        args:
+          - --autofix
+          - --no-sort-keys
+      - id: debug-statements
+
+  - repo: https://github.com/bufbuild/buf
+    rev: v1.50.1
+    hooks:
+      - id: buf-lint
+      - id: buf-format
diff --git a/.release-it.json b/.release-it.json
new file mode 100644
index 0000000000000000000000000000000000000000..b75b801c45c416155340c2859a12613520aea62b
--- /dev/null
+++ b/.release-it.json
@@ -0,0 +1,25 @@
+{
+  "git": {
+    "commitMessage": "chore(release): ${version}"
+  },
+  "gitlab": {
+    "release": true,
+    "releaseName": "${version}"
+  },
+  "plugins": {
+    "@release-it/conventional-changelog": {
+      "infile": "CHANGELOG.md",
+      "header": "# Changelog",
+      "preset": {
+        "name": "conventionalcommits"
+      }
+    },
+    "@release-it/bumper": {
+      "out": {
+        "file": "pyproject.toml",
+        "type": "text/toml",
+        "path": "tool.poetry.version"
+      }
+    }
+  }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/README.md b/README.md
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11ce4f9ff3cb31768aa2d835a3c38c21228f856d 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,22 @@
+# Message schemas
+
+This repository contains the message schemas for events published and consumed by the account
+selector and account data manager.
+
+## Generate code
+
+Generation of code is done with `buf`. Look for installation instructions here:
+
+https://github.com/bufbuild/buf
+
+Once installed, simply run:
+
+```
+buf generate --template buf.gen.yaml
+```
+
+Or use `poe`:
+
+```
+poe generate
+```
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6b67a5e5f29e07abd4dec1138a30ea8bf974d258
--- /dev/null
+++ b/buf.gen.yaml
@@ -0,0 +1,6 @@
+version: v2
+managed:
+  enabled: true
+plugins:
+  - remote: buf.build/community/danielgtaylor-betterproto:v1.2.5
+    out: "gen/python"
diff --git a/buf.lock b/buf.lock
new file mode 100644
index 0000000000000000000000000000000000000000..4f98143f52f84ab4ff38f700a8c824b3e5a911ba
--- /dev/null
+++ b/buf.lock
@@ -0,0 +1,2 @@
+# Generated by buf. DO NOT EDIT.
+version: v2
diff --git a/buf.yaml b/buf.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c7e30e3819110b6208327bfddb3626f58e42cf99
--- /dev/null
+++ b/buf.yaml
@@ -0,0 +1,9 @@
+version: v2
+modules:
+  - path: proto
+lint:
+  use:
+    - STANDARD
+breaking:
+  use:
+    - FILE
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000000000000000000000000000000000000..e6e2be38c26b43b4361ab4ad0e99e219a5b17f58
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+package = []
+
+[metadata]
+lock-version = "2.0"
+python-versions = "*"
+content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
diff --git a/proto/account_selector/v1/data.proto b/proto/account_selector/v1/data.proto
new file mode 100644
index 0000000000000000000000000000000000000000..3d28d70b39d68faa2e1bfb679b6ac5b3c689d562
--- /dev/null
+++ b/proto/account_selector/v1/data.proto
@@ -0,0 +1,35 @@
+syntax = "proto3";
+
+package account_selector.v1;
+
+message DateOfBirth {
+  message Date {
+    uint32 year = 1;
+    uint32 month = 2;
+    uint32 day = 3;
+  }
+  Date date = 1;
+  string source = 2;
+}
+
+message Code {
+  string code = 1;
+  string source = 2;
+}
+
+enum AccountEligible {
+  ACCOUNT_ELIGIBLE_UNSPECIFIED = 0;
+  ACCOUNT_ELIGIBLE_ELIGIBLE = 1;
+  ACCOUNT_ELIGIBLE_INELIGIBLE = 2;
+  ACCOUNT_ELIGIBLE_UNKNOWN = 3;
+}
+
+message Account {
+  string crsid = 1;
+  string name = 2;
+  string affiliation = 3;
+  string college = 4;
+  repeated DateOfBirth date_of_birth = 5;
+  repeated Code code = 6;
+  AccountEligible eligible = 7;
+}
diff --git a/proto/account_selector/v1/event.proto b/proto/account_selector/v1/event.proto
new file mode 100644
index 0000000000000000000000000000000000000000..15762fb95e300e582fa3d88633e4fa018746f8a0
--- /dev/null
+++ b/proto/account_selector/v1/event.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+package account_selector.v1;
+
+import "account_selector/v1/data.proto";
+import "google/protobuf/timestamp.proto";
+
+enum Operation {
+  OPERATION_UNSPECIFIED = 0;
+  OPERATION_CREATE = 1;
+  OPERATION_UPDATE = 2;
+  OPERATION_DELETE = 3;
+}
+
+message AccountSelectorEvent {
+  Operation operation = 1;
+  string crsid = 2;
+  account_selector.v1.Account account = 3;
+  google.protobuf.Timestamp valid_at = 4;
+}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..28460f14c647d72f0b7bcd54370d6af78fb7402e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,15 @@
+[tool.poetry]
+name = "message-schemas"
+version = "0.1.0"
+description = ""
+authors = [ ]
+readme = "README.md"
+package-mode = false
+
+[tool.poe.tasks.generate]
+help = "Generate code with protoc plugins"
+cmd = "buf generate --template buf.gen.yaml"
+
+[tool.poe.tasks.fix]
+help = "Run pre-commit checks to fix formatting errors"
+cmd = "pre-commit run --all-files"