Braze 입문 6편 — Liquid · Connected Content · Personalization

2026-05-17Braze 입문에서 운영까지

Braze 입문 6편. Personalization 3 layer (Static · Dynamic · Connected), Liquid templating 의 기원 (Shopify open source spec), 3 구성 (Variable · Tag · Filter) 깊이, Braze 의 Liquid 확장 (Catalog · Subscription · Random sample), Connected Content 의 외부 API lookup (timeout · cache · fallback · 보안), AI/ML 통합 (OpenAI · Claude 연동 패턴), 어디에 Liquid 박나 (메시지 · subject · URL · push payload) 까지 풀어쓴 학습 노트.

📚 Braze 입문에서 운영까지 · 6편 — Liquid · Connected Content · Personalization

이 글은 Braze 입문에서 운영까지 시리즈 6편이에요. 1편 큰 그림 부터 5편 REST API · Currents 까지 데이터 흐름과 발송 인프라를 봤다면, 이번 6편은 메시지가 진짜 개인화되는 자리 — Liquid + Connected Content.

왜 Personalization 이 중요한가

Personalization(개인화) — 메시지를 사용자별로 다르게 보여 주는 기법. 같은 캠페인이라도 효과 차이가 큽니다.

A: "여름 세일 30% 할인"
   → CTR 2.1%

B: "{{ ${first_name} }}님, 좋아하시는 {{ ${last_viewed_category} }} 30% 할인"
   → CTR 5.8% (약 3 배)

이름 한 줄과 관심 카테고리만 더해도 engagement 가 3 배. Personalization 은 ROI 의 가장 강력한 lever 입니다.

Personalization 의 3 Layer

Layer 데이터 출처 시점
Static 하드코딩 콘텐츠 작성 시
Dynamic (Liquid) User Attribute · Event · Catalog 발송 시 (Braze 안 데이터)
Connected (Connected Content) 외부 API 발송 시 (외부 시스템 호출)

세 layer 는 조합으로 동작해요. 같은 메시지 안에 static text 와 Liquid 변수, Connected Content 결과가 자연스럽게 섞입니다.

Liquid — 메시지의 templating 언어

Liquid = Shopify 가 만든 open source templating(템플릿) 언어. Braze · Klaviyo · Iterable 같은 marketing 도구가 표준으로 채택했어요.

1편의 Liquid 잠깐 소개 를 깊이 풀어 봅니다.

Liquid 의 3 구성

{{ variable }}          ← Variable (값 출력)
{% tag %}                ← Tag (제어 흐름)
{{ value | filter }}    ← Filter (변환)

세 가지 building block 이 전부. 아무리 복잡한 personalization 도 이 셋의 조합으로 풀립니다.

표시 형식

구문 의미
{{ ... }} Variable 출력
{% ... %} Tag (제어 흐름)
{% if ... %} ... {% endif %} 조건 분기
{% for ... in ... %} ... {% endfor %} 반복
{{ value | filter }} 변환 적용

다른 templating 도구 Mustache · Handlebars · Jinja 와 비슷한 syntax 예요.

Variable — 값 출력

표준 User Attribute

안녕하세요, {{ ${first_name} | default: '회원님' }}님!

회원님의 등급: {{ ${tier} | default: 'Bronze' }}
가입일: {{ ${date_of_first_session} | date: '%Y-%m-%d' }}
국가: {{ ${country} | default: '한국' }}

${...} 형식이 Braze 의 User attribute 예요. 항상 default 필터를 권장해요. 빈 값에서 오는 어색한 메시지를 막아 줍니다.

Custom Attribute

신규 사용자 카테고리: {{ ${signup_source} | default: 'organic' }}
LTV: {{ ${lifetime_value} | default: 0 | currency }}

3편 Custom Attribute 에서 만든 모든 attribute 가 Liquid 변수로 그대로 들어옵니다.

Event Property

캠페인이 event 로 trigger 됐다면 그 event 의 properties 를 꺼낼 수 있어요.

{# Event "checkout_completed" trigger 시 #}

주문 #{{ event_properties.${order_id} }} 가 접수됐어요.
금액: {{ event_properties.${cart_value} | currency }}
상품 수: {{ event_properties.${items_count} }}개

Trigger Properties (API trigger)

5편의 /campaigns/trigger/send 에서 넘긴 properties 가 여기로 들어와요.

{# /campaigns/trigger/send 의 trigger_properties #}

배송 추적: {{ ${tracking_number} }}
예상 배송: {{ ${eta} | date: '%Y-%m-%d %H:%M' }}

Canvas Entry Properties

/canvas/trigger/send 의 entry properties — journey 전체에서 살아 있어요.

캠페인 출처: {{ canvas_entry_properties.${campaign_source} }}
가입 의도: {{ canvas_entry_properties.${signup_intent} }}

Tag — 제어 흐름

if · elsif · else — 조건 분기

{% if ${tier} == 'premium' %}
  Premium 회원 전용 30% 할인 쿠폰을 드려요.
{% elsif ${tier} == 'gold' %}
  Gold 회원 20% 할인 쿠폰입니다.
{% else %}
  첫 결제 10% 할인 쿠폰을 드려요.
{% endif %}

unless — 부정 조건

{% unless ${has_completed_first_purchase} %}
  첫 구매 시 5,000원 적립금이 자동 지급돼요.
{% endunless %}

for — 반복

{# Catalog 의 추천 상품 list 출력 #}

{% catalog_items products ${last_viewed_product_ids} %}
{% for item in items %}
  - {{ item.name }} ({{ item.price | currency }})
{% endfor %}

case · when — 다중 분기

{% case ${preferred_category} %}
  {% when 'shoes' %}
    여름 슈즈 신상 30% 할인!
  {% when 'bags' %}
    가방 신상품 모음
  {% when 'accessories' %}
    액세서리 추천
  {% else %}
    오늘의 추천 상품
{% endcase %}

assign · capture — 변수 할당

{# 변수 할당 #}
{% assign discount = ${cart_value} | times: 0.1 %}

{# 복잡한 출력을 변수로 capture #}
{% capture greeting %}
  안녕하세요, {{ ${first_name} | default: '회원님' }}님!
{% endcapture %}

{{ greeting }}

Filter — 값 변환

Liquid 의 가장 강력한 자리예요. 같은 값을 다양한 형태로 바꿔 줍니다.

문자열 filter

{{ ${first_name} | upcase }}          → "JOHN"
{{ ${first_name} | downcase }}         → "john"
{{ ${email} | replace: '@', '_at_' }} → "user_at_example.com"
{{ "Hello World" | truncate: 5 }}      → "Hello..."
{{ ${name} | strip }}                  → 앞뒤 공백 제거

Date filter

{{ ${signup_date} | date: '%Y-%m-%d' }}        → "2026-05-17"
{{ ${signup_date} | date: '%Y년 %m월 %d일' }}  → "2026년 05월 17일"
{{ 'now' | date: '%H:%M' }}                     → 현재 시각

날짜 format 은 strftime(C 표준 날짜 포맷) 규약. %Y · %m · %d · %H · %M · %S 같은 토큰을 그대로 써요.

Number filter

{{ ${cart_value} | times: 0.9 }}      → 10% 할인 가격
{{ ${cart_value} | divided_by: 1000 }} → 1000 으로 나누기
{{ 50000 | money_with_currency: 'KRW' }} → 50,000원 (Braze 확장)
{{ ${value} | round: 2 }}              → 소수점 2자리
{{ ${value} | floor }}                 → 내림
{{ ${value} | ceil }}                  → 올림

Array filter

{{ ${tags} | join: ', ' }}             → "vip, premium, korea"
{{ ${tags} | size }}                    → array 길이
{{ ${tags} | first }}                   → 첫 요소
{{ ${tags} | last }}                    → 마지막 요소
{{ ${products} | sort: 'price' }}      → price 기준 정렬
{{ ${tags} | uniq }}                   → 중복 제거

Default filter — 가장 자주 쓰는

{{ ${first_name} | default: '회원님' }}
{{ ${tier} | default: 'Bronze' }}
{{ ${last_purchase_date} | default: '없음' }}

모든 personalization 의 안전선이에요. attribute 가 비어 있을 때 어색한 빈 값이 메시지에 박히는 사고를 막아 줍니다.

Filter 체이닝

{{ ${first_name} | downcase | capitalize | default: '회원님' }}
{{ ${cart_value} | times: 0.9 | round: 0 | money_with_currency: 'KRW' }}

여러 filter 를 왼쪽에서 오른쪽으로 순차 적용합니다.

Braze 의 Liquid 확장

표준 Liquid 에 Braze 만의 특수 tag/filter 가 더해져요.

Catalog tag

{% catalog_items products "${last_viewed_product_id}" %}

{% if items.first %}
  {{ items.first.name }} 이 다시 입고됐어요!
  가격: {{ items.first.price | money_with_currency: 'KRW' }}
{% endif %}

5편 Catalogs 와 결합하면 메시지 안에서 직접 상품 정보를 조회할 수 있어요.

Random sample

{% assign random_product = products | sample: 1 %}
오늘의 추천: {{ random_product.first.name }}

사용자마다 다른 상품을 노출해서 대량 발송에 다양성을 줍니다.

Subscription 검사

{% if ${email_marketing_subscription} == 'subscribed' %}
  뉴스레터 옵션을 활성 해 두셨네요. 감사합니다.
{% endif %}

abort_message — 발송 중단

{% if ${tier} == 'banned' %}
  {% abort_message('User is banned') %}
{% endif %}

안녕하세요...

조건에 걸린 해당 사용자만 발송을 건너뛰고, 캠페인 전체는 계속 진행돼요.

어디에 Liquid 박나 — 메시지의 모든 곳

필드 Liquid 사용
Email subject 안녕하세요 {{ ${first_name} }}님
Email body HTML 안 모든 콘텐츠
Push title {{ ${first_name} }}, 새 알림
Push body 메시지 본문
SMS body 짧은 메시지
In-app message 모든 콘텐츠 필드
Content Card title · description · 이미지 alt
Deep link URL myapp://product/{{ ${last_product_id} }}
Webhook URL/body API 호출 URL · payload
Connected Content URL 외부 API endpoint

거의 모든 필드에 들어가요. 메시지의 어디든 개인화가 가능합니다.

Connected Content — 외부 API lookup

Connected Content 가 답하는 자리

Liquid 만으로는 부족한 경우들이 있어요. 실시간 상품 재고(Catalogs sync 보다 신선해야 할 때), AI 추천 모델의 결과, 날씨나 환율 같은 외부 데이터, 우리 회사 다른 시스템의 사용자 데이터, 제 3자 service 의 lookup — 이런 자리를 Connected Content 가 메웁니다. 메시지를 보내기 직전 외부 API 를 호출해서 응답을 본문에 반영해요.

기본 syntax

{% connected_content
   https://api.example.com/recommendations
     ?user_id={{ ${external_id} }}
   :save recommendations
%}

추천 상품:
{% for item in recommendations.items %}
- {{ item.name }} ({{ item.price | money_with_currency: 'KRW' }})
{% endfor %}

:save 키워드로 응답을 변수에 저장해서 메시지 본문에서 그대로 활용합니다.

HTTP Method · Headers · Auth

{% connected_content
   https://api.example.com/users/recommendations
   :method post
   :headers {
     "Authorization": "Bearer YOUR_TOKEN",
     "Content-Type": "application/json"
   }
   :body { "user_id": "{{ ${external_id} }}", "limit": 5 }
   :save recommendations
   :content_type application/json
%}

GET · POST · PUT · DELETE 모두 지원하고, JSON body 자체가 Liquid 로 동적으로 채워져요.

Cache 정책

{% connected_content
   https://api.example.com/products/recommendations
   :cache_max_age 300
   :save recommendations
%}

cache_max_age(초) 로 같은 URL 응답을 지정한 시간 동안 캐싱해요. 부하가 줄고 발송이 빨라집니다.

대표 cache 시간은 상품 정보 1시간(3600초), 추천 모델 5분(300초), 재고 정보 1분(60초), 환율 1시간 정도가 무난해요.

Timeout

{% connected_content
   https://api.example.com/...
   :timeout 5
%}

기본 timeout(Braze 가 정해 둔 값) 은 보통 몇 초 수준이에요. 너무 길게 잡으면 발송이 통째로 지연됩니다.

여기서 시험 함정이 하나 있어요 — Connected Content timeout 이 곧 발송 latency. 우리 API 가 느리면 Braze 의 전체 발송이 같이 늘어져요. 우리 API 는 500ms 이내 응답이 권장됩니다.

Fallback — 외부 API 실패 시

{% connected_content
   https://api.example.com/recommendations?user_id={{ ${external_id} }}
   :save recommendations
   :rescue 'recommendations_failed'
%}

{% if recommendations_failed %}
  오늘의 추천 상품을 확인해 보세요!
{% else %}
  {% for item in recommendations.items %}
    - {{ item.name }}
  {% endfor %}
{% endif %}

:rescue 는 실패하면 지정 변수를 true 로 세팅해 줘요. 기본 메시지로 fallback 가능합니다.

핵심 원칙은 하나. Connected Content 가 실패해도 발송 자체는 진행되고, 해당 부분만 fallback 처리돼요.

AI/ML 통합 — 외부 모델 활용

Connected Content 의 가장 강력한 활용처가 AI 모델 호출이에요.

OpenAI GPT 연동

{% connected_content
   https://api.openai.com/v1/chat/completions
   :method post
   :headers {
     "Authorization": "Bearer {{ env.OPENAI_KEY }}",
     "Content-Type": "application/json"
   }
   :body {
     "model": "gpt-4o-mini",
     "messages": [
       {"role": "system", "content": "사용자 관심사 기반 짧은 추천 메시지를 작성하세요."},
       {"role": "user", "content": "사용자 카테고리: {{ ${preferred_category} }}, 최근 구매: {{ ${last_purchase_name} }}"}
     ],
     "max_tokens": 100
   }
   :save ai_message
   :cache_max_age 3600
   :rescue 'ai_failed'
%}

{% if ai_failed %}
  새로운 상품을 만나 보세요!
{% else %}
  {{ ai_message.choices.first.message.content }}
{% endif %}

개인화된 AI 메시지가 본문 안으로 자동 삽입돼요.

Anthropic Claude 연동

{% connected_content
   https://api.anthropic.com/v1/messages
   :method post
   :headers {
     "x-api-key": "{{ env.ANTHROPIC_KEY }}",
     "anthropic-version": "2023-06-01",
     "content-type": "application/json"
   }
   :body {
     "model": "claude-3-5-sonnet-20241022",
     "max_tokens": 100,
     "messages": [{
       "role": "user",
       "content": "고객 {{ ${first_name} }} 에게 맞는 짧은 추천 메시지 (한국어)"
     }]
   }
   :save claude_response
   :cache_max_age 3600
   :rescue 'claude_failed'
%}

{{ claude_response.content.first.text | default: '오늘의 추천' }}

자체 ML 모델 추천

{% connected_content
   https://ml.internal.example.com/recommend
     ?user_id={{ ${external_id} }}
     &category={{ ${preferred_category} }}
     &k=5
   :save recommendations
   :cache_max_age 600
%}

우리 회사의 ML(머신러닝) 모델 API 를 호출해서 학습된 추천 결과를 메시지에 그대로 실어요.

AI 사용의 함정

정말 중요한 시험 함정 — AI 응답의 prompt injection(프롬프트 조작 공격) 과 부적절 콘텐츠 위험이에요.

사용자 attribute "first_name" = "; DROP TABLE users; --"
  → LLM prompt 에 그대로 들어가면 위험

또는

사용자가 input field 에 욕설 박음
  → AI 가 그 단어를 그대로 메시지에
  → 모든 사용자에게 욕설

해결은 네 갈래로 갑니다. attribute 를 마케팅 시스템 통과 전에 정제(sanitize) 하고, AI prompt 의 system message 에 "한국어로 추천만, 욕설·공격 X" 같은 강력한 가드를 박고, AI 응답을 키워드 필터와 length check 로 후처리하고, 의심스러운 응답은 fallback 으로 돌립니다.

AI 통합의 비용

GPT-4o-mini: ~$0.15 / 1M input tokens, $0.60 / 1M output
캠페인 100K 사용자 × 200 token = 20M token = ~$3 ~ $12

Claude Haiku: ~$0.80 / 1M input, $4 / 1M output (조금 비쌈)

캠페인 1회 발송이 몇 달러에서 수십 달러 선이에요. cache_max_age 와 fallback 을 함께 묶으면 비용이 통제됩니다.

보안 — Connected Content 함정

URL Injection

{# 나쁜 패턴 #}
{% connected_content
   https://api.example.com/lookup?id={{ ${user_input} }}
%}

# user_input = "1; rm -rf /" 또는 SQL injection 같은 공격

${...} 가 자동으로 URL-encoded 되는지는 Braze 가 어느 정도 보호해 줘요. 다만 완벽 의존은 위험합니다. 우리 API 측에서도 validation 을 걸어 둬야 해요.

Sensitive Data 노출

{# 위험 — 외부 API 응답에 PII 포함 시 메시지에 박힘 #}
{% connected_content
   https://api.example.com/user-details?id={{ ${external_id} }}
   :save user_details
%}

전화: {{ user_details.phone }}    ← PII 노출
사회보장번호: {{ user_details.ssn }}    ← 심각한 위험

Connected Content 의 응답 자체가 PII(개인식별정보) safe 하도록 우리 API 를 설계해야 해요. 민감 데이터는 응답 schema 에서 아예 제외합니다.

Rate Limit Hit (외부 API)

대량 캠페인이면 수십만 명에게 Connected Content 호출이 동시에 나가요. 우리 API 가 Rate Limit hit 을 맞을 수 있습니다.

해결은 세 가지. cache_max_age 를 적극 활용하고, 우리 API 를 Cache·CDN(콘텐츠 전송 네트워크) 으로 scaling 하고, 발송 직전에 background pre-warming 으로 cache 를 채워 둡니다.

Liquid 사용 패턴 — 5 실전

Pattern 1: 안전한 personalization

안녕하세요, {{ ${first_name} | default: '회원님' }}님!

{% if ${tier} %}
회원님의 등급: {{ ${tier} | upcase }}
{% endif %}

{% if ${last_purchase_date} %}
마지막 구매일: {{ ${last_purchase_date} | date: '%Y-%m-%d' }}
{% endif %}

모든 변수에 default 를 두고 조건 확인까지 거치면 어색한 빈 값을 피할 수 있어요.

Pattern 2: A/B 테스트 (Liquid 분기)

{% assign variant = ${external_id} | md5 | slice: 0, 1 %}

{% if variant == '0' or variant == '1' %}
  [A 그룹] 빨강 버튼 캠페인
{% else %}
  [B 그룹] 초록 버튼 캠페인
{% endif %}

사용자 ID 의 hash 로 deterministic(결정적·재현 가능한) 그룹 분할을 합니다. A/B 가 매번 같은 결과로 재현돼요.

Pattern 3: 다국어 — locale 분기

{% case ${language} %}
  {% when 'ko' %}
    안녕하세요, {{ ${first_name} | default: '회원님' }}님!
  {% when 'ja' %}
    こんにちは、{{ ${first_name} | default: 'お客様' }}様!
  {% when 'en' %}
    Hello, {{ ${first_name} | default: 'there' }}!
  {% else %}
    Hello, {{ ${first_name} | default: 'there' }}!
{% endcase %}

한 캠페인으로 모든 언어 사용자를 커버해요. Liquid 분기가 자동 번역 역할을 합니다.

Pattern 4: Catalog + 추천

{% catalog_items products ${recently_viewed_ids} %}

{% if items.size > 0 %}
{{ ${first_name} | default: '회원님' }}님, 최근 본 상품을 확인해 보세요:

{% for item in items %}
- {{ item.name }} ({{ item.price | money_with_currency: 'KRW' }})
{% endfor %}

지금 다시 확인하기:
{% for item in items %}
  myapp://product/{{ item.id }}
{% endfor %}
{% else %}
오늘의 인기 상품을 만나 보세요!
{% endif %}

5편 Catalogs 와 결합한 실시간 상품 메시지예요.

Pattern 5: AI personalization + fallback

{% connected_content
   https://api.openai.com/v1/chat/completions
   :method post
   :headers {
     "Authorization": "Bearer {{ env.OPENAI_KEY }}",
     "Content-Type": "application/json"
   }
   :body {
     "model": "gpt-4o-mini",
     "messages": [{
       "role": "system",
       "content": "한국어로 짧은 (3 문장 이하) 마케팅 메시지를 작성. 욕설·공격적 표현·민감 데이터 절대 포함 X."
     }, {
       "role": "user",
       "content": "고객 {{ ${first_name} | default: '회원님' }}, 카테고리 {{ ${preferred_category} | default: '의류' }}"
     }],
     "max_tokens": 150,
     "temperature": 0.7
   }
   :save ai_response
   :cache_max_age 3600
   :timeout 5
   :rescue 'ai_failed'
%}

{% if ai_failed %}
  안녕하세요, {{ ${first_name} | default: '회원님' }}님!
  오늘의 새로운 상품을 만나 보세요.
{% else %}
  {{ ai_response.choices.first.message.content }}
{% endif %}

운영 환경에서 표준이 된 AI personalization 형이에요. cache + timeout + fallback + 안전 가드가 한 묶음으로 들어가 있습니다.

자주 만나는 사고

사고 1: Default 누락 → 어색한 빈 값

원인은 {{ ${first_name} }} 만 박은 거예요. 해결은 모든 personalization 에 | default: '...' 를 빠짐없이 거는 것.

사고 2: Liquid syntax 오류

{% if x %} 가 누락되거나 {% endif %} 가 빠지면 해당 사용자만 발송이 실패해요. 발송 전에 Liquid preview 도구로 한 번 돌려 보거나, test 사용자에게 미리 보내 봅니다.

사고 3: Connected Content timeout

우리 API 가 5초를 넘기면 Braze 가 timeout 을 잡고 fallback 또는 실패로 넘어가요. 우리 API 의 500ms 이내 응답을 보장하고, cache 를 적극 활용합니다.

사고 4: Connected Content 의 데이터 누설

외부 API 응답에 PII 가 섞이면 그대로 메시지에 박힙니다. 우리 API 측에서 발송용 응답 schema 를 분리하고 PII 를 제외해야 해요.

사고 5: AI prompt injection

사용자 input 이 LLM prompt 에 그대로 들어가면 의도 외 위험 콘텐츠가 나올 수 있어요. input sanitization 과 system prompt 강력 가드, 응답 후처리 필터 — 세 겹으로 막습니다.

사고 6: 대량 발송 시 외부 API rate limit

100K 사용자에 Connected Content 가 한 번씩 호출되면 100K 외부 API 호출이 한꺼번에 나가요. cache_max_age 를 적극 쓰고, 우리 API 를 scaling 하고, background pre-warming 까지 묶으면 견딥니다.

사고 7: AI 응답 비결정성

temperature: 0.7 이면 같은 사용자도 매번 다른 응답이 나와요. 일관성이 부족해 보입니다. temperature: 0.3 처럼 낮게 두고 cache 를 함께 쓰면 같은 사용자에게 같은 응답이 갑니다.

사고 8: Catalog 안 item 없을 때

{% catalog_items %} 가 빈 결과를 돌려주면 items.first 가 nil 이 되고 메시지에 오류가 납니다. {% if items.size > 0 %} 가드를 걸고 fallback 메시지를 준비해 둬요.

운영 권장 패턴

Pattern 1: Personalization 안전선 — 4 layer

{# 1. 가장 안전한 fallback #}
{% assign safe_name = ${first_name} | default: '회원님' | strip %}

{# 2. validation #}
{% if safe_name == '' or safe_name == nil %}
  {% assign safe_name = '회원님' %}
{% endif %}

{# 3. 사용 #}
안녕하세요, {{ safe_name }}님!

{# 4. 안 보이게 강제 (verbose 한 메시지 회피) #}
{% capture _ %}{{ random_field | default: '' }}{% endcapture %}

다층 안전선이에요.

Pattern 2: AI personalization 셋업

operational_pattern:
  - cache_max_age: 3600         # 1시간 cache
  - timeout: 5                   # 5초 한도
  - fallback: 기본 마케팅 메시지 (AI 실패 시)
  - max_tokens: 150              # 응답 길이 한도
  - temperature: 0.3             # 일관성 우선
  - system_prompt:
      - "한국어"
      - "짧게 (3 문장 이하)"
      - "욕설·공격적 표현 X"
      - "민감 데이터 X"
      - "추천 형식"
  - response_filter:
      - keyword_block: ["기밀", "비밀", "..."]
      - length_check: < 200 chars

운영 표준입니다.

Pattern 3: Connected Content 의 우리 API 설계

# 발송용 추천 API
@app.get("/marketing/recommendations/{user_id}")
async def get_marketing_recommendations(user_id: str):
    # 1. Cache 확인 (CDN · Redis)
    cached = await cache.get(f"rec:{user_id}")
    if cached:
        return cached

    # 2. 추천 계산
    recommendations = await recommendation_service.get(user_id, limit=5)

    # 3. 응답 schema — PII 제외
    response = {
        "items": [
            {
                "id": r.product_id,
                "name": r.product_name,
                "price": r.price,
                "image_url": r.image_url
                # 의도적으로 user 의 phone · email · ssn 제외
            }
            for r in recommendations
        ]
    }

    # 4. Cache 저장
    await cache.set(f"rec:{user_id}", response, ttl=600)

    return response

marketing-safe response schema 와 fast cache 가 핵심이에요.

Pattern 4: Liquid 검증 워크플로

1. Console 의 Liquid Preview 도구 사용
2. Test segment (10명 정도) 에 미리 발송
3. test 사용자 actual 메시지 검수
4. 변수 누락 · 어색한 메시지 detect
5. 수정 후 production 발송

production 폭격을 피하는 흐름이에요.

Pattern 5: Multi-language 표준화

{% assign lang = ${language} | default: 'ko' %}

{% case lang %}
  {% when 'ko' %}
    {% assign greeting = '안녕하세요' %}
    {% assign closing = '감사합니다' %}
  {% when 'ja' %}
    {% assign greeting = 'こんにちは' %}
    {% assign closing = 'ありがとうございます' %}
  {% else %}
    {% assign greeting = 'Hello' %}
    {% assign closing = 'Thank you' %}
{% endcase %}

{{ greeting }}, {{ ${first_name} | default: '...' }}!

[본문]

{{ closing }}.

다국어 메시지의 일관 구조입니다.

시험 직전 한 번 더 — Liquid · Connected Content 함정 압축 노트

Personalization 3 Layer

  • Static — 하드코딩 (작성 시)
  • Dynamic (Liquid) — Braze 안 데이터 (User · Event · Catalog · Trigger · Canvas Entry)
  • Connected — 외부 API lookup (발송 직전)

Liquid 3 구성

  • Variable = {{ var }} (값 출력)
  • Tag = {% tag %} (제어 흐름)
  • Filter = {{ value | filter }} (변환)

Variable 종류

  • ${attribute} — User Attribute
  • event_properties.${prop} — Event 의 properties
  • ${trigger_properties.${prop}} — API trigger
  • canvas_entry_properties.${prop} — Canvas entry
  • Catalog · Subscription state 등

Tag 핵심 5

  • {% if %} ... {% elsif %} ... {% else %} ... {% endif %}
  • {% unless %} ... {% endunless %}
  • {% for x in list %} ... {% endfor %}
  • {% case %} {% when %} {% endcase %}
  • {% assign var = ... %} · {% capture %}

Filter 카테고리

  • String — upcase · downcase · replace · truncate · strip
  • Date — date: '%Y-%m-%d' (strftime 표준)
  • Number — times · divided_by · round · floor · ceil · money_with_currency
  • Array — join · size · first · last · sort · uniq · sample
  • Default — 가장 자주 (모든 personalization 의 안전선)
  • Chaining — | filter1 | filter2 | filter3

Braze 확장

  • {% catalog_items %} — Catalog lookup
  • {% connected_content %} — 외부 API
  • {% abort_message %} — 발송 중단 (해당 사용자만)
  • ${email_marketing_subscription} — Subscription state

Liquid 사용 위치 — 거의 모든 필드

  • Email subject · body · sender name
  • Push title · body · deep link
  • SMS body
  • In-app · Content Card 의 모든 텍스트
  • Webhook URL · body
  • Deep Link URL

Connected Content 핵심

  • 외부 API 호출 (메시지 발송 직전)
  • HTTP GET/POST/PUT/DELETE + headers + body
  • :save 응답 저장
  • :cache_max_age (초) — 응답 캐싱
  • :timeout (초) — 한도
  • :rescue 'var' — 실패 시 fallback
  • timeout = 발송 latency → 우리 API 500ms 이내 권장

AI 통합

  • OpenAI · Claude · 자체 모델 모두 Connected Content 로
  • Cache 적극 — 같은 사용자 같은 응답
  • Fallback 필수 — AI 실패 시 기본 메시지
  • Prompt injection 함정 — system prompt 강력 가드 + input sanitization + 응답 후처리
  • 비용 = 캠페인 100K = 몇 $ ~ 수십 $ (model 별)
  • 운영 표준 — max_tokens · low temperature · cache · timeout · fallback

보안 함정

  • URL Injection — 우리 API 측 validation 도 추가
  • Sensitive Data 노출 — 우리 API 응답 schema 에서 PII 제외
  • Rate Limit (외부 API) — cache + scaling + pre-warming
  • AI prompt injection — system prompt + sanitize + filter

사고 함정

  • Default 누락 ("안녕하세요, 님!")
  • Liquid syntax 오류 (해당 사용자 발송 실패)
  • Connected Content timeout (느린 API)
  • 외부 API 의 PII 노출
  • AI prompt injection
  • 대량 발송 외부 API rate limit
  • AI 응답 비결정성 (temperature 낮게)
  • Catalog 빈 결과 (가드)

운영 패턴

  • Personalization 4 layer 안전선
  • AI 셋업 표준 (cache · timeout · fallback · 가드)
  • 우리 API marketing-safe schema
  • Test segment 미리 발송
  • Multi-language 표준화

공식 문서: Braze Liquid · Connected Content 에서 원문을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!