Repository navigation
Add "Sync Ortto contact" event for v6 contact sync (giveth-v6-core#426) - #136
Conversation
New ORTTO-category NotificationType (givethio) that upserts an Ortto
person WITHOUT sending an email. Unlike "Create Ortto profile" it always
merges on the stable v6 user id (str:cm:v6-user-id) regardless of
environment, so a canonical-email change re-points the same contact
instead of creating a duplicate, and it stamps a durable
bol:cm:sourced-from-v6 marker so v6-managed contacts are distinguishable
from legacy v5-sourced ones.
- notifications types: SYNC_ORTTO_CONTACT event + reuse the existing
"created-profile" Ortto activity (no new Ortto activity required)
- general/notificationType: NOTIFICATION_TYPE_NAMES + schemaValidator
- segment validator syncOrttoContact { email, userId required; names
optional/blank so wallet-only & Turnkey profiles still sync }
- activityCreator case + always-merge-by-v6-user-id + marker
- seed migration for the new NotificationType
Requires the Ortto workspace to define custom fields str:cm:v6-user-id
and bol:cm:sourced-from-v6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(#426) Adversarial-review fixes for the Sync Ortto contact event: - callOrttoActivity now returns a boolean success (logs, does not throw); sendNotification 502s the Sync Ortto contact event when the Ortto call fails, so v6-core sees a non-2xx and its reconcile cron retries instead of recording a false success. Other Ortto events are unchanged (fire-and-forget; the boolean is ignored). - Point the sync at a DEDICATED inert Ortto activity (act:cm:sync-ortto-contact) instead of reusing act:cm:created-profile, so a contact sync (which fires again on every canonical-email re-point) can never re-trigger a created-profile welcome journey/email — the sync must be side-effect-free. Ortto workspace prerequisites: activity `sync-ortto-contact` (no automation bound) and custom fields `str:cm:v6-user-id`, `bol:cm:sourced-from-v6`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (3)
WalkthroughAdds a Sync Ortto contact notification type, validates and normalizes its payload, constructs a v6 identity-based Ortto activity, and maps Ortto outcomes to explicit HTTP errors. ChangesOrtto Contact Sync
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant NotificationService
participant JoiValidator
participant OrttoAdapter
participant Ortto
Caller->>NotificationService: Submit Sync Ortto contact payload
NotificationService->>JoiValidator: Validate and coerce payload
JoiValidator-->>NotificationService: Return normalized contact data
NotificationService->>OrttoAdapter: Send v6 identity merge activity
OrttoAdapter->>Ortto: Submit activity with timeout
Ortto-->>OrttoAdapter: Return success or failure
OrttoAdapter-->>NotificationService: Return OrttoActivityResult
NotificationService-->>Caller: Return success, 422, or 502
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/adapters/emailAdapter/orttoAdapter.ts (1)
7-37: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winPrevent the Ortto upsert from blocking the sync response.
callOrttoActivityawaitsaxios.request(config)with no finitetimeout, andsendNotificationwaits for this promise before it can throw the502used for contact-sync retries. Add a small, validated request timeout so a stalled Ortto connection fails back through the caller’s error path instead of keeping the request indefinitely pending.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adapters/emailAdapter/orttoAdapter.ts` around lines 7 - 37, Update callOrttoActivity’s axios request configuration to include a small finite timeout, sourced from an existing configuration value or validated constant, so stalled Ortto requests reject promptly and preserve sendNotification’s 502 retry path.
🧹 Nitpick comments (1)
src/services/notificationService.ts (1)
57-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd regression coverage for the new cross-layer contract.
Test the exact activity ID,
merge_by, v6 marker field, optional names, and both adapter outcomes:falsemust produce HTTP 502 only forSYNC_ORTTO_CONTACT, while existing Ortto events remain non-throwing.Also applies to: 226-245, 359-377
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/services/notificationService.ts` around lines 57 - 64, Extend regression tests around the SYNC_ORTTO_CONTACT handling to assert the exact activity ID, merge_by value, v6 marker field, and support for omitted optional first and last names. Cover both adapter outcomes: a false result returns HTTP 502 only for SYNC_ORTTO_CONTACT, while existing Ortto event types remain non-throwing and retain their current behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/emailAdapter/orttoAdapter.ts`:
- Around line 28-37: Update the error handling in orttoActivityCall so
logger.error does not include the full data payload or raw Axios error/config.
Log only a safe allowlist of non-sensitive identifiers and a sanitized error
summary, excluding email, names, v6-user-id, headers, and request configuration.
---
Outside diff comments:
In `@src/adapters/emailAdapter/orttoAdapter.ts`:
- Around line 7-37: Update callOrttoActivity’s axios request configuration to
include a small finite timeout, sourced from an existing configuration value or
validated constant, so stalled Ortto requests reject promptly and preserve
sendNotification’s 502 retry path.
---
Nitpick comments:
In `@src/services/notificationService.ts`:
- Around line 57-64: Extend regression tests around the SYNC_ORTTO_CONTACT
handling to assert the exact activity ID, merge_by value, v6 marker field, and
support for omitted optional first and last names. Cover both adapter outcomes:
a false result returns HTTP 502 only for SYNC_ORTTO_CONTACT, while existing
Ortto event types remain non-throwing and retain their current behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6e386be8-3da2-4edd-b689-d632eb18c574
📒 Files selected for processing (10)
migrations/1732000000000-seedNotificationTypeSyncOrttoContact.tssrc/adapters/emailAdapter/orttoAdapter.tssrc/adapters/emailAdapter/orttoAdapterInterface.tssrc/adapters/emailAdapter/orttoMockAdapter.tssrc/entities/notificationType.tssrc/services/notificationService.tssrc/types/general.tssrc/types/notifications.tssrc/utils/errorMessages.tssrc/utils/validators/segmentAndMetadataValidators.ts
…136) - orttoAdapter: never log the payload (contact email / names / v6-user-id) or the raw Axios error (its `config` carries the X-Api-Key header and body); log only a sanitized summary (microService, activity ids, HTTP status, error message). - orttoAdapter: add a finite request timeout (ORTTO_REQUEST_TIMEOUT_MS, default 10s, validated) so a stalled Ortto connection rejects promptly instead of keeping the request — and the contact-sync 502 retry path — pending forever. - Document ORTTO_REQUEST_TIMEOUT_MS in config/example.env. - Add activityCreator regression tests for SYNC_ORTTO_CONTACT: dedicated inert activity id, merge_by str:cm:v6-user-id (env-independent), sourced-from-v6 marker, and optional-names omission. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/services/notificationService.test.ts`:
- Around line 112-129: Update the activity builder used by activityCreator for
SYNC_ORTTO_CONTACT so firstname and lastname attributes are added only when
values are supplied, rather than with undefined values. Extend the existing
nameless-profile test to assert that result.activities[0].attributes excludes
both str:cm:firstname and str:cm:lastname while preserving the existing
attributes.
- Around line 90-109: Update the test’s ENVIRONMENT cleanup around the
activityCreator scenario to track whether process.env.ENVIRONMENT existed before
setting it. In the finally block, restore the saved value when the variable
originally existed; otherwise delete process.env.ENVIRONMENT instead of
assigning the undefined value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d47a8b2f-5cf6-4fbd-a36b-70ebc6d44bc3
📒 Files selected for processing (3)
config/example.envsrc/adapters/emailAdapter/orttoAdapter.tssrc/services/notificationService.test.ts
|
Addressed the CodeRabbit review in
Typecheck clean; the new test assertions validated. |
…cleanup - activityCreator (SYNC_ORTTO_CONTACT): include str:cm:firstname / str:cm:lastname only when supplied, so a nameless profile never sends `undefined` attributes. - notificationService.test: assert the nameless activity omits both name attributes; restore process.env.ENVIRONMENT by delete-when-originally-unset instead of assigning undefined (which would leave the string "undefined"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the re-review in
Typecheck clean; assertions validated. |
Code review — P0–P2Reviewed at extra-high effort. Build is clean ( 🔴 P01. Every Ortto failure maps to a retryable 502, so a permanent 4xx is retried forever
This matters because of the deploy ordering the PR itself describes: the code ships before the manual Ortto workspace edit. During that window Ortto returns 400 on every request — the activity Suggested fix: branch on 🟠 P12. Joi's coerced value is discarded, so a string
Verified against joi 17.6:
Three distinct Suggested fix: 3. A request with no Joi treats Per this PR's own contract, v6-core treats non-2xx as retryable — so a permanently malformed request is retried indefinitely instead of being rejected once. Suggested fix: guard 4. Ortto's rejection body is no longer logged, making 400s undiagnosable The sanitized log keeps Combined with P0 above, an operator sees an endless 502 loop and cannot tell whether the custom field, the activity, or an attribute is the problem. Suggested fix: add 5. The "confirmed upsert" guarantee has silent-success holes The 502 fires only inside
In both cases control falls through to Suggested fix: for this event, treat a missing validator or falsy 6. The 502 path has no test, and the mock adapter makes it untestable The three new tests cover only Nothing in CI exercises the new throw, so every regression it introduces — the batch abort below, 4xx-as-retryable-502, the silent-success holes — ships green. Suggested fix: an injectable/stub adapter that can return 🟡 P27. One Ortto 502 rejects the entire bulk batch
Suggested fix: 8. The new 10s timeout silently changes behavior for every pre-existing Ortto event
The PR states "All other Ortto events keep their existing fire-and-forget behavior", but their success rate is now bounded by a new 10s ceiling. Suggest scoping the timeout to this event, or confirming p99 Ortto latency before picking 10s. 9.
Suggested fix: 10. The PII-sanitization rationale is contradicted two lines above, and in the caller The catch-block comment says the contact's email/names/v6-user-id must NEVER be logged, but the success path logs each full activity ( So the sanitization removes diagnostics (see P1 #4) without actually keeping PII out of the logs. Either redact those debug logs too, or stop justifying the loss of 11. The seed migration isn't idempotent, and it gates server startup
Suggested fix: an idempotent upsert ( 🤖 Review generated with Claude Code |
… false success
- P0/P1: callOrttoActivity now returns {ok,retryable,status,responseBody};
sendNotification maps a transient Ortto failure (5xx/timeout/network) to 502
and a permanent one (4xx — bad payload / unprovisioned field/activity) to 422
for Sync Ortto contact, so a permanent error isn't retried as if transient.
- P1: log Ortto's rejection body (no api key / no PII) so a 4xx is diagnosable.
- P1: syncOrttoContact schema now userId Joi.number().integer().positive() and
email Joi.string().trim().lowercase().email(); validateWithJoiSchema returns
the coerced value and the sync forwards it, so one v6 user always maps to one
stable merge key (no ' 42 '/'042'/'4e1' duplicates) and a bad email is rejected.
- P1: close the silent-success holes — a missing segment (400), a missing
segment validator (500), or a falsy activityCreator result (500) now fail the
Sync Ortto contact request instead of returning {success:true}.
- P2: scope the request timeout to the sync event only (other Ortto events keep
their prior no-timeout behavior).
- P2: seed migration is now idempotent (find-or-insert) so a pre-existing row
can't fail up() and block staging startup.
- Tests: adapter 4xx/5xx/network classification + response-body; sync validator
coercion/rejection; nameless-activity attribute omission; safe env cleanup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @RamRamez — thorough review. Addressed in P0 #1 (4xx→502 retried forever): P1 #2 (userId coercion → duplicate merge keys): schema is now P1 #3 (missing segment → 500 loop): explicit P1 #4 (rejection body not logged): now logs P1 #5 (silent-success holes): for the sync event, a missing segment validator (500) and a falsy P1 #6 (no test for the throw): added P2 #8 (global timeout): the timeout is now applied only to the sync event; all other Ortto events keep their prior no-timeout behavior. P2 #9 (email not validated): P2 #10 (PII-log contradiction): resolved by #4 — the catch comment no longer justifies withholding P2 #11 (non-idempotent migration gates startup): Deferred — P2 #7 (bulk tsc clean; new logic validated standalone (validator + adapter classification). |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
migrations/1732000000000-seedNotificationTypeSyncOrttoContact.ts (1)
47-50: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPreserve notification types that this migration did not insert.
Lines 35-40 accept a pre-existing row and skip insertion. TypeORM still marks the migration as applied. Line 49 then deletes that pre-existing AdminJS or manually seeded row during rollback.
Record seed ownership and delete only a row created by this migration. If ownership cannot be persisted, make
downpreserve the row rather than deleting data this migration does not own.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@migrations/1732000000000-seedNotificationTypeSyncOrttoContact.ts` around lines 47 - 50, Update the migration’s up/down logic around the notification type seed to track whether this migration actually inserted the “Sync Ortto contact” row. Make down delete only a row owned by this migration, and preserve pre-existing rows when insertion was skipped or ownership was not persisted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/emailAdapter/orttoMockAdapter.ts`:
- Around line 12-17: Update callOrttoActivity in OrttoMockAdapter so
logger.debug no longer receives the complete data payload; log only
non-sensitive activity identifiers and the microService, ensuring mock-adapter
logs do not retain contact PII.
In `@src/services/notificationService.ts`:
- Around line 366-370: Move the isSyncOrttoContact determination before the
duplicate-track check in the notification handling flow. Update the
duplicate-track success path to bypass its short-circuit for SYNC_ORTTO_CONTACT,
allowing validation and the Ortto upsert to run; preserve existing duplicate
behavior for other events. Add a regression test covering contact sync with an
existing trackId.
---
Outside diff comments:
In `@migrations/1732000000000-seedNotificationTypeSyncOrttoContact.ts`:
- Around line 47-50: Update the migration’s up/down logic around the
notification type seed to track whether this migration actually inserted the
“Sync Ortto contact” row. Make down delete only a row owned by this migration,
and preserve pre-existing rows when insertion was skipped or ownership was not
persisted.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: dc956b8a-200f-409e-9e6e-0dfc366196c2
📒 Files selected for processing (10)
migrations/1732000000000-seedNotificationTypeSyncOrttoContact.tssrc/adapters/emailAdapter/orttoAdapter.test.tssrc/adapters/emailAdapter/orttoAdapter.tssrc/adapters/emailAdapter/orttoAdapterInterface.tssrc/adapters/emailAdapter/orttoMockAdapter.tssrc/services/notificationService.test.tssrc/services/notificationService.tssrc/utils/errorMessages.tssrc/utils/validators/segmentAndMetadataValidators.tssrc/validators/schemaValidators.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/utils/validators/segmentAndMetadataValidators.ts
- src/utils/errorMessages.ts
…pter log - notificationService: determine isSyncOrttoContact before the duplicate-trackId check and exclude the sync event from that short-circuit, so a duplicate trackId can never skip the Ortto upsert and return a false success (v6-core would mark the contact synced and stop retrying). The sync sends no trackId today; this makes the guarantee explicit. - orttoMockAdapter: log only activity ids + microservice, never the full activity payload (email / names / v6-user-id) — no contact PII in logs even under EMAIL_ADAPTER=mock (CWE-532). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed both in
|
Re-review — remaining issuesRe-reviewed at head 🟠 P11. The 502/422 path is untested, and the hook added to test it is dead code
The reply defers the end-to-end assertion to "the DB-backed harness (runs in CI)" — that harness already exists and is already green: So every new throw site ships unexercised: the 400 missing-payload guard ( Fix: one router test that sets 🟡 P22. Mock adapter was redacted; the production adapter still logs the full contact
data.activities.map((a: any) => logger.debug('orttoActivityCall', a));Runs on every call and writes the whole activity — email, first/last name, Default log level is Two things make this worth fixing rather than waving through as pre-existing: CodeRabbit's identical CWE-532 objection was accepted for the mock adapter in Fix: apply the same 3. Ortto workspace prerequisite is still missing the required attributesPR description · The reply states the required activity attributes were documented in the Ortto prereqs. I can't find it — not in the PR body, not in any code comment on the branch. The current prerequisite text covers only the two custom fields (
Fix: add the four attribute names to the prerequisite section. 4. The 4xx/5xx split has no effect until giveth-v6-core#441 acts on it
The classification itself is correct and well tested (2xx / 400 / 503 / ECONNREFUSED all verified). But per your own note, v6-core treats all non-2xx as retryable — so 422 and 502 are indistinguishable at the consumer today, and a permanent 4xx still loops every 30 minutes forever, which was the substance of the original P0. Not a defect in this repo; the remaining half of the fix lives in giveth-v6-core#441. Flagging so it's an explicit merge gate rather than an assumption: confirm #441 marks 4xx as permanent (skip / dead-letter) before this ships, or the P0 is closed on paper only. 🔵 P35. Strict
|
| input | result |
|---|---|
user@localhost |
✗ "email" must be a valid email |
user@example.eth |
✗ |
user@example.crypto |
✗ |
user@münchen.de |
✓ |
user+tag@gmail.com |
✓ |
v6 emails are user-entered and largely unverified, so classic TLD typos (.con, .cmo) land here. Those users 400 on every attempt and — combined with #4 — are re-attempted every 30 minutes indefinitely while never converging.
Rejecting a broken address is defensible; the unbounded retry isn't. Fixing #4 largely neutralises this. If you'd rather not depend on that, .email({ tlds: { allow: false } }) keeps structural validation while letting Ortto arbitrate the TLD.
Verdict
Approve pending P1 #1 — cheap to close since the hook already exists. P2 #2–#4 and P3 #5 are fine as a fast-follow, with the caveat that #4 is a cross-repo gate, not a cleanup.
🤖 Review generated with Claude Code
…nly, TLD
- P1: add router tests driving the in-process mock adapter's result — 502
(retryable), 422 (non-retryable), 200 (confirmed), and another Ortto event
still 200 on ok:false (fire-and-forget preserved). Exercises the
sendNotification status-mapping the adapter unit tests don't reach; the
nextResult hook is now used.
- P2: redact the production adapter's success-path debug log (activity ids +
microservice only, never the full activity/PII) and drop the raw segment
payload from sendNotification's debug log (keys only) — matches the mock
redaction and the catch-block's own PII rule (CWE-532).
- P2: align the sync path to identity-only — activityCreator + segment schema
now carry just email + userId (no names), so the Ortto activity only needs
the str:cm:email and str:cm:v6-user-id attributes declared.
- P3: relax email validation to email({ tlds: { allow: false } }) — keeps the
structural check but doesn't reject unusual-but-real TLDs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Re-review addressed in P1 #1 (502/422 path untested; nextResult dead code) — fixed. Added router tests in P2 #2 (prod adapter still logs the full contact) — fixed. P2 #3 (Ortto prereq attributes undocumented) — resolved + reduced. The sync is now identity-only — P2 #4 (4xx/5xx split inert until #441 acts) — closed. #441 now applies exponential backoff to failed pushes (bounded retry, not every-30-min-forever). It deliberately does not dead-letter 4xx: a 422 during the deploy window (activity/fields not yet provisioned in Ortto) is transient-until-configured and must still converge, so uniform backoff is the correct model. #441 lands after this PR (deploy order unchanged). P3 #5 (strict TLD blocks typos → infinite retry) — fixed.
|
Summary
Companion notification-center change for Giveth/giveth-v6-core#426 (Sync v6 users to Ortto as contacts by canonical email). Adds a dedicated event that upserts an Ortto contact without sending any email. Pairs with giveth-v6-core PR #441 — deploy this first so the new NotificationType is seeded before v6-core starts emitting the event (otherwise v6's requests 400 with
INVALID_NOTIFICATION_TYPE).Changes
Sync Ortto contactevent (NOTIFICATIONS_EVENT_NAMES/NOTIFICATION_TYPE_NAMES), ORTTO category,givethiomicroservice, with a seed migration for theNotificationType.activityCreatorcase that, for this event, always merges the Ortto person on the stablestr:cm:v6-user-id(regardless ofENVIRONMENT) so a canonical-email change re-points the same contact instead of creating a duplicate, and stamps a durablebol:cm:sourced-from-v6person-field marker so v6-managed contacts stay distinguishable from legacy v5 ones. Uses a dedicated inert activityact:cm:sync-ortto-contact(notcreated-profile) so the sync can never re-fire a welcome journey.syncOrttoContactsegment validator (email,userIdrequired;firstName/lastNameoptional/blank) so nameless wallet/Turnkey profiles still sync.callOrttoActivitynow returns a boolean success (logs, still doesn't throw); for theSync Ortto contactevent,sendNotificationreturns a 502 when the Ortto upsert fails, so v6-core records a false success only when the upsert is confirmed and its reconcile cron retries otherwise. All other Ortto events keep their existing fire-and-forget behavior.Deploy prerequisite (Ortto workspace)
The Ortto workspace must define custom fields
str:cm:v6-user-idandbol:cm:sourced-from-v6, and the activitysync-ortto-contactwith no automation/journey bound to it.How to Test
yarn db:migrate:run:local) so theSync Ortto contacttype is seeded; setEMAIL_ADAPTER=mock./v1/thirdParty/notifications(Basic auth, givethio) with{ eventName: "Sync Ortto contact", segment: { payload: { email, userId } } }.str:cm:v6-user-id, setsbol:cm:sourced-from-v6: true, usesact:cm:sync-ortto-contact, and that no email is sent.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes