Skip to content

feat(team-provisioner): make forwarding to personal email opt-in - #193

Open
Alexanderamiri wants to merge 3 commits into
mainfrom
feat/forwarding-toggle
Open

Alexanderamiri wants to merge 3 commits into
mainfrom
feat/forwarding-toggle

Conversation

@Alexanderamiri

Copy link
Copy Markdown
Member

Summary

Mail to java.no accounts now stays in the java.no inbox. Forwarding to a member's personal email is opt-in, with forward_to_personal: true in the registry's members.yaml. It goes through the Gmail routing rule "Personlige aliaser", which an admin updates by hand (the rule has no API). The Lambda no longer touches Gmail forwarding.

 team-provisioner  _sync_hero_account
-  _setup_email_forwarding(java.no, personal)   # new and 409 paths; also the gmail.settings.sharing scope
 scripts/
+  gmail-address-map.py   opted-in members → "java.no, personal" lines for the routing rule
   provision-groups.py
+    opt-in turned on/off or personal_email changed → print + Slack reminder (java.no addresses only)
   diff-groups.py
+    plan comment marks forward_to_personal on new and changed members
   sort-members.py, sync-members.py
+    keep forward_to_personal when rewriting members.yaml (the sheet never sets it)

Background: since 2026-10-24, the routing rule forwarded all 64 members' mail to their personal emails with "Also route to original destination" OFF, so java.no inboxes got nothing. It was disabled today. The Lambda's Gmail API forwarding barely took effect: per-user forwarding is on for one member only (bjorn.hamre@java.no), possibly set by himself, and has been left as it is.

Evidence

  • Before: with the new tests and the old code, python3 -m unittest scripts/tests/test_forwarding.py terraform/lambda-src/tests/test_team_provisioner_forwarding.py → FAILED (failures=1, errors=5)
    After: python3 -m unittest discover -s scripts/tests → 239 OK; -s terraform/lambda-src/tests → 270 OK

Review focus

  • scripts/provision-groups.py forwarding_changes / post_forwarding_reminder: personal emails must never reach Slack. test_main_posts_reminder_without_personal_emails covers this through main().
  • handler.py _sync_hero_account: new accounts still get the welcome mail, and nothing calls the Gmail settings API.

Out of scope: provision-groups.yml only diffs against HEAD~1, so a multi-commit push misses earlier changes (predates this PR).

Merge danger

Door: two-way
Blast radius: new hero accounts

Prod via platform CI (Lambda code update). Merge before javaBin/registry feat/forwarding-toggle, because registry workflows run platform main's sort-members.py, and the old one strips the flag.

After merge:

  1. Remove gmail.settings.sharing from the domain-wide delegation client (Admin console > Security > API controls > Domain-wide delegation). No code needs it any more, so it only lets the SA key set forwarding on any mailbox.
  2. When the first member opts in, check that mail to their alias is forwarded too. The map lists primary addresses only.

OpenSpec

spec-free: the repo has no openspec/; behaviour is documented in docs/lambda-functions.md and CLAUDE.md.

🤖 Generated with Claude Code

Alexanderamiri and others added 3 commits November 11, 2026 18:54
New accounts no longer get Gmail API forwarding to their personal email (it
only sent Google's confirmation mail; forwarding came from the "Personlige
aliaser" routing rule, which forwarded every member's mail away from the
java.no inbox and is now disabled). Forwarding is opt-in per member with
`forward_to_personal: true` in the registry's members.yaml:

- scripts/gmail-address-map.py prints the recipient address map for opted-in
  members, for an admin to paste into the routing rule (it has no API)
- provision-groups.py posts a reminder when the opted-in members change
- diff-groups.py shows the flag in the registry plan comment
- the Lambda drops _setup_email_forwarding and the gmail.settings.sharing scope

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sort-members.py and sync-members.py rebuild members.yaml from known fields;
both now keep the opt-in, and the sheet sync never sets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner November 11, 2026 18:02
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 0 to add, 1 to change, 0 to destroy.

Plan output
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.lambdas.aws_lambda_function.team_provisioner will be updated in-place
  ~ resource "aws_lambda_function" "team_provisioner" {
        id                             = "javabin-team-provisioner"
      ~ last_modified                  = "2026-11-11T17:43:29.000+0000" -> (known after apply)
      ~ source_code_hash               = "WAVhnqy7u14WWq5Jd9Cm0Gl3MjAcj0FTvfjPWPYUxkc=" -> "H0CZQR5raH4IFPYQQCynqd8cItwUzUfBVugccrL7M2E="
        tags                           = {}
        # (21 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan makes a single in-place update to the Lambda function javabin-team-provisioner, changing only its source code hash (new deployment package); no creates or destroys.

  • ✅ [routine] module.lambdas.aws_lambda_function.team_provisioner is updated in-place: source_code_hash changes from WAVhnqy7... to H0CZQR5r..., indicating a new code artifact is being deployed. Function configuration (role, env, memory, timeout) is not changing per the plan output.
  • 🔒 [security] No IAM, security group, or public-access changes are present in this plan. The function's execution role is unchanged, so no permission grants or boundary changes are introduced. Review the code diff in the source commit that produced the new hash before applying, since this Lambda provisions teams.
  • 💰 [cost] No new billable resources are created. No NAT gateways, RDS, or load balancers are involved. Cost impact is negligible.
  • 💥 [destruction] Plan shows 0 to add, 0 to destroy. No data loss or force-replacement; the change is an in-place update.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant