5 שלב הבנייה

ה-Glue — Auth, Email, Tunnel ו-Analytics שמשלימים אפליקציה אמיתית

Worker פרוס הוא לא עדיין אפליקציה. חסרים לו ארבעה דברים: הגנה מפני בוטים, login, דרך לקבל ולענות על email, ועין שרואה מה קורה בפנים. בפרק הזה נחבר את כל ה-glue — Turnstile, Zero Trust Access, named tunnel, Email Routing ו-Analytics Engine — כולם ב-$0, עם הבנה מדויקת איזו מגבלה חינמית נשברת ראשונה בכל אחד מהם (ולמה send של email זה הסיפור היחיד פה שעולה כסף).

מה יהיה לך ביד בסוף הפרק
מטרות למידה
מה צריך לפני שמתחילים
חוט הפרויקט

הפרקים הקודמים (1-4): יש לך Worker פרוס + R2 + KV + D1 מפרקים 1-2, שכבת AI/RAG מפרק 3, ושכבת media מפרק 4 — וכבר השתמשת ב-Quick Tunnel מפרק 1 כדי לחשוף localhost.

הפרק הזה: מוסיפים את שכבת ה-glue שהופכת את כל זה לאפליקציה אמיתית: הגנת bot (Turnstile), זהות (Access), חשיפה יציבה (named tunnel), email נכנס (Email Routing) ועין (Analytics Engine). כולם על אותו wrangler.toml, כולם ב-$0 — מלבד שליחת email יזומה, שהיא הדבר היחיד פה שדורש Workers Paid.

הפרק הבא (פרק 6 — Full-Stack חינמי): שכבת ה-glue הזו נכנסת ל-capstone — SaaS חי full-stack מגודר ב-Turnstile + Access עם observability ב-Analytics Engine, ולצדו מטריצת ה-pay-or-not שממפה לכל primitive את המגבלה שנופלת ראשונה.

מילון מונחים — פרק 5
מונחבעבריתהסבר
Turnstileטרנסטיילה-CAPTCHA הבלתי נראה והחינמי של Cloudflare. מאמת שמשתמש אנושי בלי challenge מציק, ומציג checkbox רק כשצריך. חינם ללא מגבלת נפח; עד 20 widgets לחשבון.
server-side token validationאימות טוקן בצד שרתאימות הטוקן של Turnstile בצד ה-Worker מול endpoint ה-siteverify עם ה-secret key. חובה — בדיקת הלקוח קוסמטית וניתנת לזיוף.
Zero Trust / Accessזירו-טראסט / אקססשירות Cloudflare שמגדר כל URL מאחורי אימות זהות (Google / GitHub / email-OTP) לפני שהבקשה מגיעה ל-origin — בלי לכתוב קוד auth. 50 seats חינם (hard-limit).
email OTPקוד חד-פעמי לאימיילהתחברות בקוד חד-פעמי שנשלח לאימייל — שיטת זהות ש-Access מספק בלי סיסמאות ובלי DB משתמשים. כל אימייל ייחודי נספר כ-seat.
Cloudflare Tunnelטאנלחיבור outbound-only שחושף שירות לוקלי לאינטרנט בלי פורט פתוח או IP ציבורי. שני מצבים: Quick ו-Named.
named tunnelטאנל בעל-שםTunnel קבוע עם דומיין מותאם, ניהול מרחוק, ללא תקרת concurrent ועם תמיכת SSE — לעומת Quick Tunnel המוגבל.
cloudflaredקלאודפלייר-דיה-binary שמריץ את ה-Tunnel. מ-2026 Wrangler מוריד ומנהל אותו אוטומטית דרך פקודות wrangler tunnel.
Email Routingניתוב אימיילניתוב email נכנס ל-Worker לעיבוד / forward / reply — חינם ללא הגבלה בכל הטיירים (קבלה בלבד).
Email Serviceשירות אימיילה-productization (beta, אפריל 2026) של שליחת email מ-Worker — Workers Paid בלבד, 3,000/חודש כלול ואז $0.35/1,000 (כמתועד).
Analytics Engineמנוע אנליטיקהtime-series DB בתוך Workers — כותבים events מקוד ושואלים ב-SQL. 100k data points/יום, כרגע unbilled (beta).
data pointsנקודות-נתוןיחידת הכתיבה של Analytics Engine — רשומה עם blobs (מימדי string), doubles (מדדים מספריים) ו-index יחיד. נכתבת fire-and-forget.
Secrets Storeמאגר סודותמנהל secrets ברמת חשבון לשיתוף בין Workers (beta) — 20 secrets חינם, Workers-only, מחיר טרם פורסם.
מתחילים קונספט $0 8 דקות

מה זה ה-Glue, ולמה Worker עירום הוא לא אפליקציה

פרסת Worker. הוא חי על URL, מחזיר HTML, מדבר עם D1 ו-R2. אבל אם תיתן את ה-URL הזה ללקוח אמיתי או תפרסם אותו ברשת, תוך יום תגלה שחסרים לו דברים שאף primitive של אחסון או AI לא נותן: בוטים ימלאו את הטופס בזבל, כל אחד יכול להיכנס ל-/admin, אין דרך לקבל email מהמשתמשים, ואין לך מושג כמה אנשים בכלל נכנסו ומה הם עשו.

הדברים האלה הם ה-glue — שכבת הדבק שמחברת את הלוגיקה שכתבת לעולם האמיתי. ב-stack רגיל היית מוסיף לכל אחד מהם שירות צד-שלישי בתשלום: reCAPTCHA, Auth0, ngrok בתשלום, SendGrid, Google Analytics. ב-Cloudflare כל החמישה האלה כבר יושבים ליד ה-Worker שלך, רובם בחינם, ומתחברים אליו ב-binding או בשורת config — בלי שרת נוסף ובלי חשבון חדש.

למה זה משנה ל-vibe coder ספציפית: כשאתה בונה עם כלי AI, הקוד מגיע מהר — אבל שכבת ה-glue היא בדיוק המקום שבו פרויקטים נתקעים. מוסיפים ספריית auth ופתאום צריך להבין JWT ו-refresh tokens; רוצים CAPTCHA ונכנסים ל-API keys של ספק חיצוני; צריך email ופתאום יש חשבון SMTP לתחזק. כל אחד מאלה הוא תלות חדשה, חשבון חדש, וחיוב חדש. הגישה של הפרק הזה הפוכה: במקום להוסיף שירותים, להפעיל יכולות שכבר קיימות. ה-Worker שלך כבר רץ על הרשת של Cloudflare — Turnstile, Access, Tunnel, Email ו-Analytics הם פשוט מתגים שאתה מדליק על אותה תשתית.

חמשת חורי ה-glue, וה-primitive שסותם כל אחד

הכלל המנחה של הפרק: כל רכיב glue פה הוא $0 בנקודת ההתחלה — חוץ מחור אחד ספציפי, שליחת email יזומה, שדורש Workers Paid. נחזור לזה שלוש פעמים בפרק כי כל מדריך שני מבלבל בין קבלה (חינם) לשליחה (בתשלום).

עשו עכשיו 3 דקות

פתח את ה-Worker שפרסת בפרק 1-2 וכתוב לעצמך ב-3 שורות מה עדיין חסר לו כדי שתיתן אותו ללקוח אמיתי: יש לו הגנת bot? יש login? מישהו יכול לשלוח לו email? אתה יודע כמה אנשים נכנסו? סמן אילו מהחמישה אתה צריך — זה ה-glue שתחבר בפרק הזה.

בוא נתחיל מהחור הראשון שכל אפליקציה ציבורית נתקלת בו ביום הראשון: בוטים.

מתחילים קונספט $0 10 דקות

Turnstile — CAPTCHA בלתי נראה וחינמי לחלוטין

Turnstile הוא ה-CAPTCHA של Cloudflare, והתכונה החשובה ביותר שלו היא שלמשתמש אנושי הוא לרוב בלתי נראה. במקום לבחור אופנועים בתמונות, Turnstile מריץ smart challenge ברקע — בודק אותות של הדפדפן ומחליט אם מולו אדם. רק כשהוא לא בטוח הוא מציג checkbox פשוט. אין פאזלים, אין תמונות.

ולמה זה מתאים בדיוק ל-vibe coder: Turnstile חינם לחלוטין, ללא מגבלת נפח. אין תקרת אימותים ביום, אין כרטיס אשראי. זה אחד הרכיבים הבודדים ב-Cloudflare שאין לו מגבלה חינמית שנשברת ראשונה — הוא פשוט חינם. המגבלות היחידות הן מבניות: עד 20 widgets לחשבון, עד 10 hostnames ל-widget, ו-7 ימי analytics לאחור. (Enterprise פותח unlimited widgets, 200 hostnames ו-30 ימי analytics — לא רלוונטי ל-free tier.)

מה שכדאי להבין מהמגבלות האלה: הן מבניות, לא נפחיות. 20 widgets זה לא "20 אימותים" — זה 20 הגדרות נפרדות של widget, ואתה יכול להשתמש באחד מהם למיליון אימותים. רוב הפרויקטים צריכים widget אחד או שניים (אחד ל-production, אחד ל-staging), אז התקרה הזו כמעט אף פעם לא רלוונטית. מגבלת 7 ימי ה-analytics אומרת רק שהגרף ב-dashboard מראה שבוע אחורה — אם אתה רוצה היסטוריה ארוכה יותר, אתה תרשום את האירועים בעצמך ל-Analytics Engine (שנגיע אליו בהמשך הפרק). כלומר: בניגוד לכל primitive אחר בקורס, פה אין "מגבלה שנופלת ראשונה" שצריך לתכנן סביבה. זה נדיר, ושווה לנצל.

site key מול secret key — שני מפתחות, שני צדדים

כשתיצור widget תקבל שני מפתחות, וההבחנה ביניהם היא הלב של Turnstile:

הזרימה כולה: הלקוח פותר את ה-challenge ומקבל טוקן. הטוקן נשלח לשרת. ה-Worker שולח את הטוקן + ה-secret key ל-Cloudflare כדי לוודא שהוא אמיתי. נראה את זה בקוד בסעיף הבא.

implicit render מול explicit render

יש שתי דרכים להזריק את ה-widget לדף:

דוגמה מייצגת — הצד הלקוח (implicit render)
<!-- ב-<head> -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<!-- בתוך ה-form: implicit מזריק input נסתר בשם cf-turnstile-response -->
<form action="/submit" method="POST">
  <input type="text" name="email" />
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">שלח</button>
</form>

נקודה קריטית שנחזור אליה: הטוקן תקף 300 שניות, הוא single-use (חד-פעמי), וניתן לזיוף. לכן הבדיקה בצד הלקוח היא קוסמטית בלבד — האימות האמיתי חייב לקרות בשרת.

איזה mode לבחור

כשתיצור widget תתבקש לבחור mode, וההבדל ביניהם הוא כמה אגרסיבי ה-challenge:

בפועל, התחל ב-Managed. אם תראה שהמשתמשים מתלוננים על checkbox מיותר, תוכל לכוונן — אבל זה נדיר עם Managed.

למה לא reCAPTCHA

אם בנית באינטרנט בעבר, ה-reflex שלך יהיה reCAPTCHA של Google. שני הבדלים מעשיים: ראשית, Turnstile לא שולח את התנועה של המשתמשים שלך ל-Google — הוא first-party ל-Cloudflare. שנית, ה-UX טוב יותר: אין "בחר את כל הרמזורים". ושלישית, אתה כבר על Cloudflare — אין integration נוסף. בשביל vibe coder שרוצה הגנת bot בלי כאב ראש, Turnstile הוא ברירת המחדל הטבעית.

עשו עכשיו 4 דקות

היכנס ל-dashboard ← TurnstileAdd widget, בחר מצב Managed, והעתק את ה-site key ל-clipboard. אל תיגע בקוד עדיין — רק תוודא שיש לך widget ושני המפתחות מולך.

בינוני מעשי $0 14 דקות

שילוב Turnstile ב-Worker — אימות טוקן בצד שרת

עכשיו לחצי השני, שבלעדיו ה-Turnstile חסר ערך: server-side token validation. ה-Worker מקבל את הטוקן מה-form, ושולח אותו יחד עם ה-secret key ל-endpoint ה-siteverify של Cloudflare. רק התשובה משם קובעת אם להמשיך או להחזיר 403.

קודם — לאחסן את ה-secret key נכון

ה-secret key הוא סוד. הוא לא הולך בקוד, לא ב-wrangler.toml, ולא בצד הלקוח. הוא נכנס דרך wrangler secret put — בדיוק כמו שעשית בפרק 2:

דוגמה מייצגת — רישום ה-secret
npx wrangler secret put TURNSTILE_SECRET_KEY
# הדבק את ה-secret key מה-dashboard כשהוא מבקש
npx wrangler secret list   # ודא שהוא מופיע

הזרימה: client → token → Worker → siteverify → allow/deny

לקוח widget + form Worker מקבל cf-turnstile-response siteverify secret + token success → OK false → 403 token
דוגמה מייצגת — אימות בצד Worker
// Worker: אימות הטוקן של Turnstile בצד שרת
export default {
  async fetch(request, env) {
    const body = await request.formData();
    const token = body.get('cf-turnstile-response');
    const ip = request.headers.get('CF-Connecting-IP');

    const form = new FormData();
    form.append('secret', env.TURNSTILE_SECRET_KEY); // לעולם לא בצד לקוח
    form.append('response', token);
    form.append('remoteip', ip);

    const res = await fetch(
      'https://challenges.cloudflare.com/turnstile/v0/siteverify',
      { method: 'POST', body: form }
    );
    const outcome = await res.json();
    // outcome = { success, challenge_ts, hostname, 'error-codes': [], action, cdata }
    if (!outcome.success) {
      return new Response('Bot check failed', { status: 403 });
    }
    return new Response('OK');
  },
};

שלוש נקודות שמפילות אנשים פה:

טעות נפוצה: לאמת Turnstile בצד הלקוח / להניח "free forever" על Analytics Engine

למה זה מפתה: ה-callback של ה-widget מחזיר טוקן ונראה כאילו "עבר אימות" — אז למה לטרוח עם שרת? ובמקביל, Analytics Engine היום לא מחויב כלל, אז קל להניח שהוא חינם לתמיד.

למה זה טעות: הטוקן של Turnstile ניתן לזיוף ובדיקת הלקוח קוסמטית בלבד — בוט יכול לשלוח טוקן מזויף ישר ל-endpoint שלך ולעקוף widget שלא אומת בשרת. ובאשר ל-Analytics Engine: Cloudflare כבר פרסמה מחיר מתוכנן ($0.25 ל-מיליון writes + $1 ל-מיליון reads) — ה-unbilled הוא זמני.

מה לעשות במקום: תמיד הרץ siteverify בצד שרת עם ה-secret key (דרך wrangler secret put); אל תקבע ארכיטקטורה שמניחה ש-Analytics Engine יישאר חינם לנצח.

תרגיל: טופס מוגן ב-Turnstile עם אימות בצד Worker 25 דקות
  1. צור Turnstile widget ב-dashboard במצב Managed, קבל site key + secret key.
  2. הוסף ל-Static Assets טופס עם <script src="...turnstile/v0/api.js"> ו-<div class="cf-turnstile" data-sitekey="..."> עם ה-site key.
  3. הרץ npx wrangler secret put TURNSTILE_SECRET_KEY והדבק את ה-secret key.
  4. ב-Worker: קרא את cf-turnstile-response מה-formData, ושלח POST ל-siteverify עם secret + response + remoteip.
  5. אם outcome.success === false — החזר 403; אחרת עבד את הטופס והחזר OK.
  6. הרץ npx wrangler deploy, הגש את הטופס פעם תקינה (תקבל OK בלי challenge), ופעם עם טוקן חסר/מזויף (תקבל 403).

פלט נראה לעין: endpoint חי שמחזיר OK להגשה אמיתית ו-403 כשה-Turnstile token חסר או מזויף — ואפס challenges למשתמש אנושי.

בינוני קונפיגורציה $0 (עד 50 seats) 14 דקות

Zero Trust Access — לגדר URL לפי זהות בלי לכתוב קוד auth

Turnstile מסנן בוטים, אבל הוא לא יודע מי אתה. ברגע שאתה צריך login — "רק חברי הצוות יכולים להיכנס ל-/admin" — אתה בדרך כלל מתחיל בכאב ראש: ספריית auth, ניהול סיסמאות, טבלת משתמשים, איפוסים, hashing. Zero Trust Access מבטל את כל זה.

הרעיון: Access מגדר URL מאחורי אימות זהות. כשמשתמש מגיע, Cloudflare מאמת אותו (דרך Google, GitHub, או email-OTP) לפני שהבקשה בכלל מגיעה ל-Worker שלך. אם הוא לא עבר — הוא לא רואה את ה-origin בכלל. אתה לא כותב אף שורת קוד auth, ואין לך DB משתמשים לתחזק.

שווה לעצור על העומק של זה. ב-auth רגיל, הבקשה מגיעה ל-Worker שלך, ה-Worker בודק token/cookie ומחליט אם להחזיר 401. כלומר ה-Worker שלך חשוף — הוא רץ, צורך CPU, ורק אז דוחה. ב-Access ההיגיון הפוך: הגידור קורה ב-edge של Cloudflare, לפני שה-Worker מתעורר בכלל. בקשה לא-מאומתת לא מגיעה לקוד שלך, לא צורכת לך invocation מתוך 100k היומיים, ולא יכולה לנצל באג בקוד ה-auth שלך — כי אין קוד auth.

מה ש-Access נותן בלי שתכתוב שורה: דף login מעוצב, אינטגרציה עם Google/GitHub/SAML, ניהול sessions ו-TTL, ואכיפת policies. מה שאתה מאבד: שליטה דקה בלוגיקת ההרשאות (זו policy ב-dashboard, לא קוד), והתקרה של 50 seats שנדבר עליה מיד. ל-admin panel או beta מוזמן זו עסקה מצוינת; לאפליקציה ציבורית גדולה — לא.

email מורשה → OTP → פנימה email לא-מורשה → חסום Access שער זהות (default-deny) Worker בלי קוד auth רק מאומתים

הניווט ב-dashboard של 2026

שים לב — זה הסעיף הכי רגיש-לזמן בפרק. ב-2026 Cloudflare שינתה את שמות התפריטים. הנתיב הנוכחי הוא:

Zero Trust → Access controls → Applications → Create new application → Self-hosted and private

מדריכים ישנים אומרים "Access → Applications → Self-hosted" — אם השמות אצלך שונים מאלה שכאן, אל תיבהל: חפש את האפשרות שמדברת על self-hosted application, היא אותה אחת גם אם המילים זזו. הנחה כללית: כל צעד UI בסעיף הזה ובתרגיל 2 עלול להיראות מעט אחרת אצלך — אמת מול ה-dashboard החי.

policy: default-deny + Allow

אחרי שיצרת את ה-application, אתה מצרף אליו Access policy. הדפוס הבטוח הוא default-deny: כברירת מחדל אף אחד לא נכנס, ואתה מוסיף כלל Allow שמתאים על Emails (email-OTP) עם רשימת המיילים המורשים — ו/או על IdP כמו Google/GitHub. בנוסף קובעים Session Duration — כמה זמן הטוקן תקף לפני שצריך לאמת מחדש.

למה דווקא default-deny ולא ההפך: בכל מערכת הרשאות, הברירת מחדל הבטוחה היא "אסור אלא אם הותר במפורש". אם תתחיל מ"הכל מותר חוץ מ-X", שכחה של מקרה קצה אחד פותחת חור. עם default-deny, גרוע ביותר שיקרה הוא שמישהו לגיטימי לא יכנס ויפנה אליך — לא שמישהו לא מורשה ייכנס. ב-Access זה מתורגם לפרקטיקה פשוטה: אל תוסיף כלל "Allow everyone" כדי "לבדוק שזה עובד", ואל תשכח אותו אחר כך. בדוק עם email מורשה אחד מ-incognito.

על Session Duration: זה ה-TTL של הטוקן ש-Cloudflare נותן למשתמש אחרי אימות מוצלח. קצר מדי (שעה) — המשתמשים יתאמתו שוב ושוב וזה מעצבן; ארוך מדי (שבוע) — אם מכשיר נגנב, יש חלון גישה רחב. ל-admin panel פנימי 24 שעות זה איזון סביר. הנקודה החשובה: Cloudflare מאמתת את הטוקן בכל בקשה ב-edge, אז אתה לא צריך לנהל את זה בקוד שלך כלל.

עשו עכשיו 4 דקות

ב-Zero Trust dashboard לך ל-Access controls → Applications ופשוט סייר במסך Create new application. אתר את האפשרות Self-hosted and private — מבלי ליצור עדיין. שים לב אם השם אצלך שונה מ-tutorials ישנים, ורשום לעצמך את הנתיב המדויק שראית.

טעות נפוצה: לחשוף כלי ציבורי מאחורי Zero Trust Access

למה זה מפתה: Access נותן login יפהפה בלי קוד — מתפתה לגדר הכל מאחוריו, כולל כלי שאתה רוצה לפרסם לעולם.

למה זה טעות: התוכנית החינמית היא hard-limit של 50 seats. ב-email-OTP כל אימייל ייחודי שמתחבר נספר כ-seat. כלי ציבורי שיותר מ-50 אנשים מנסים להיכנס אליו מאלץ אותך ל-Teams ב-$7/user/month — שמתרחב לינארית, ומהר: 200 משתמשים = $1,400 בחודש.

מה לעשות במקום: השתמש ב-Access רק לקהל סגור וקטן (<50: צוות, admin panel, beta מוזמן). לכלי ציבורי — Turnstile להגנת bot + auth קל משלך (למשל magic link על KV), לא Access.

מסגרת החלטה: כלכלת ה-50 seats של Access — מתי לגדר ומתי לא

שאלה 1 — כמה אנשים ייכנסו? אם הקהל סגור וקטן (<50 משתמשים: צוות, admin, beta מוזמן) → Zero Trust Access חינם, אפס קוד auth. זה ה-use-case המושלם.

שאלה 2 — האם זה כלי ציבורי/פתוח? אם יותר מ-50 אנשים עשויים להתחבר → אל תגדר ב-Access. תקפוץ ל-$7/user/month שמתרחב מהר.

שאלה 3 — מה כן לציבורי? Turnstile (הגנת bot, חינם) + auth קל משלך (magic link / OTP על KV/D1). זה נשאר $0 בלי תקרת seats.

כלל ברזל: email-OTP סופר כל אימייל ייחודי כ-seat. אם אתה לא יכול לנקוב במספר המשתמשים מראש — אל תסמוך על Access החינמי.

תרגיל: Worker מגודר ב-Zero Trust Access עם email-OTP 25 דקות
  1. לך ל-Zero Trust → Access controls → Applications → Create new application → Self-hosted and private (אמת את השמות מול ה-UI החי).
  2. הוסף public hostname (דומיין שנמצא על Cloudflare) — או חבר את ה-Worker/origin דרך ה-named tunnel מהסעיף הבא.
  3. צור Access policy: default-deny + כלל Allow על Emails (email-OTP) עם רשימת מיילים מורשים.
  4. קבע Session Duration (למשל 24 שעות).
  5. התחבר מ-incognito: הזן email מורשה → הזן את ה-OTP שהגיע → קבל גישה. נסה email לא-מורשה → חסום.
  6. פתח את קוד ה-Worker ואמת שלא נכתבה בו אף שורת קוד auth.

פלט נראה לעין: URL חי שדורש email-OTP login (עד 50 seats) לפני שהבקשה מגיעה ל-Worker — בלי ספריית auth ובלי DB משתמשים.

בינוני מעשי $0 13 דקות

Cloudflare Tunnel — מ-Quick ל-Named tunnel יציב

בפרק 1 השתמשת ב-Quick Tunnel: wrangler tunnel quick-start נתן לך URL ציבורי ב-HTTPS ל-localhost תוך שניות, בלי חשבון. מצוין לדמו מהיר. אבל יש לו שתי מגבלות שמתחילות לכאוב ברגע שהאפליקציה אמיתית:

Named tunnel מסיר את שלושתם: דומיין מותאם יציב, ניהול מרחוק מה-dashboard, אין תקרת concurrent, ו-SSE עובד. זה ה-tunnel שתחבר ל-Access ותיתן ללקוח.

למה SSE זה לא פרט טכני שולי: אם בנית בפרק 3 את ה-RAG chat שמזרים תשובה מילה-מילה (streaming), או בפרק 4 כל UI שמעדכן בזמן אמת — אתה משתמש ב-Server-Sent Events. זו טכניקה שבה השרת משאיר חיבור פתוח ודוחף עדכונים ברצף. Quick Tunnel לא יודע להחזיק את החיבור הפתוח הזה — הוא סוגר אותו, וה-streaming פשוט מפסיק באמצע בלי שגיאה. תראה את התשובה הראשונה ואז שקט. זה אחד הבאגים הכי מתסכלים לאבחון כי שום דבר לא "נשבר" — פשוט אין יותר נתונים. הפתרון היחיד הוא named tunnel.

למה זה outbound-only ולמה אכפת לך: ה-tunnel יוצר חיבור מהמחשב שלך החוצה ל-Cloudflare, ולא להפך. כלומר אתה לא פותח פורט ב-router, לא צריך IP ציבורי, וה-firewall שלך לא צריך חריגה. זה גם בטוח יותר — אין port פתוח שמישהו יכול לסרוק. בשביל לחשוף localhost או dev server לדמו, זה בדיוק מה שאתה רוצה.

פקודות wrangler tunnel (מרץ 2026)

עד לא מזמן היית מתקין את cloudflared בנפרד. מ-2026-03-19 Wrangler מנהל אותו בשבילך — בהרצה ראשונה הוא יציע להוריד את ה-binary ל-cache המקומי, ואתה רק מאשר. ששת ה-subcommands:

דוגמה מייצגת — ניהול tunnels מ-Wrangler
# Quick tunnel — URL ציבורי מיידי, בלי חשבון (חלופת ngrok)
npx wrangler tunnel quick-start

# Named tunnel — יציב, דומיין מותאם, מנוהל מרחוק
npx wrangler tunnel create my-app
npx wrangler tunnel list
npx wrangler tunnel info my-app
npx wrangler tunnel run my-app
npx wrangler tunnel delete my-app
# נוסף 2026-03-19; ה-binary של cloudflared יורד אוטומטית בהרצה ראשונה

אזהרת freshness: פקודות ה-wrangler tunnel מסומנות experimental ונכון למרץ 2026 עלולות להשתנות. הדפוס הבטוח: קבע את גרסת wrangler ב-package.json כדי שהפקודות לא ישברו תחת רגליך, ובדוק את ה---help אם משהו לא תואם.

עשו עכשיו 3 דקות

הרץ npx wrangler tunnel list. אם cloudflared לא מותקן — אשר את ההורדה ל-cache. ראה אילו tunnels כבר קיימים בחשבון שלך (כנראה רשימה ריקה אם זו הפעם הראשונה).

מסגרת החלטה: Quick Tunnel מול Named Tunnel — מתי כל אחד

שאלה 1 — האם זה דמו חד-פעמי? אם צריך URL מהיר ללא חשבון, בלי streaming, לכמה דקות → Quick Tunnel (quick-start, trycloudflare).

שאלה 2 — האם יש SSE/streaming? כל UI עם SSE (כמו ה-RAG chat מפרק 3 או streaming מפרק 4) → חובה Named Tunnel. Quick Tunnel יפיל את ה-SSE בשקט.

שאלה 3 — דומיין יציב או >200 concurrent? דמו קבוע ללקוח, חיבור ל-Access, או יותר מ-200 בקשות במקביל → Named Tunnel (create + run).

כלל ברזל: אם יש streaming או שאתה נותן את ה-URL למישהו אחר — named tunnel. Quick Tunnel הוא רק לבדיקה מהירה משלך.

טעות נפוצה: להשתמש ב-Quick Tunnel ל-streaming/SSE UI

למה זה מפתה: Quick Tunnel עובד מצוין ל-endpoints רגילים, אז קל להניח שהוא יעבוד גם ל-chat המוזרם מפרק 3 — והוא הכי קל להרצה.

למה זה טעות: Quick Tunnels (trycloudflare) מפילים SSE בשקט ומגבילים ל-200 concurrent — ה-streaming מפרק 3/4 פשוט לא יזרום, בלי שגיאה ברורה, ותבזבז שעה על דיבאג של "למה הצ'אט תקוע".

מה לעשות במקום: לכל UI עם SSE/streaming או יותר מ-200 concurrent — wrangler tunnel create + run (named tunnel). זה הצעד שהופך את ה-RAG chat מפרק 3 ל-demo יציב.

תרגיל: named tunnel יציב לדומיין מותאם (תומך SSE) 20 דקות
  1. npx wrangler tunnel create my-app (אשר הורדת cloudflared אם נדרש).
  2. npx wrangler tunnel list ו-info my-app — ודא שה-tunnel נוצר.
  3. הרץ שירות לוקלי (Worker dev או אפליקציה) על פורט מקומי.
  4. npx wrangler tunnel run my-app וחבר אותו לדומיין מותאם על Cloudflare. כדי שה-tunnel ינתב תעבורה לדומיין שלך צריך להגדיר ingress route: דרך הדאשבורד — Zero Trust → Networks → Tunnels → [tunnel שלך] → Public Hostnames → Add a public hostname (בחר את הדומיין/zone וקבע service ל-http://localhost:<port>). חלופה דרך CLI: npx wrangler tunnel route dns my-app subdomain.example.com שמוסיף CNAME ב-Cloudflare DNS אוטומטית. שתי הדרכים מגיעות לאותה תוצאה.
  5. בדוק endpoint רגיל, ואז endpoint SSE/streaming — ודא שה-SSE עובד (בניגוד ל-Quick Tunnel).
  6. npx wrangler tunnel delete my-app בסיום אם זה היה זמני.

פלט נראה לעין: named tunnel חי שמגיש דומיין מותאם יציב כולל endpoint SSE שעובד — לעומת Quick Tunnel שמפיל SSE.

בינוני מעשי $0 12 דקות

Email Routing — לעבד email נכנס ב-Worker (חינם, unlimited)

עד עכשיו ה-Worker רק קיבל HTTP. Email Routing נותן לו ערוץ קלט חדש לגמרי: email. אתה מגדיר כתובת (למשל support@yourdomain.com) שמנותבת ישר ל-Worker, וכל מייל שמגיע אליה מפעיל handler בקוד שלך. זה חינם ללא הגבלה בכל הטיירים — כל עוד מדובר ב-קבלה (נחזור להבחנה הקריטית הזו בסעיף הבא).

ה-email() handler ב-Module Worker

בדיוק כמו ש-fetch הוא ה-handler ל-HTTP, email הוא ה-handler ל-mail נכנס. הוא חי על אותו export default:

דוגמה מייצגת — handler ל-email נכנס
// Email Routing: handler ל-email נכנס (חינם, unlimited)
export default {
  async email(message, env, ctx) {
    // message: from, to, headers (Headers), raw (ReadableStream), rawSize
    const subject = message.headers.get('subject');

    // אפשרות A: forward ליעד מאומת
    await message.forward('me@example.com');

    // אפשרות B: לדחות (bounce)
    // message.setReject('Address not accepting mail');

    // אפשרות C: auto-reply (דורש EmailMessage לתשובה)
    // await message.reply(replyMessage);
  },
};

ה-message הוא אובייקט ForwardableEmailMessage עם השדות:

ושלוש מתודות פעולה: forward(rcptTo) ליעד מאומת, reply(EmailMessage) לתשובה אוטומטית, ו-setReject(reason) ל-bounce עם שגיאת SMTP. חשוב: ה-syntax הישן של Service Worker הוסר — רק Module Worker (export default) נתמך.

שתי מלכודות תפעוליות: forward() עובד רק ליעד שאימתת מראש ב-Email Routing (לא לכל כתובת שתרצה), וכדי שכתובת המקור תעבוד הדומיין שלה חייב Email Routing פעיל.

מה אפשר לבנות עם email נכנס

הערוץ הזה פותח דפוסים שלמים שבדרך כלל דורשים שירות צד-שלישי:

הנקודה: ה-email() handler הוא JavaScript מלא. כל מה שאתה יכול לעשות ב-fetch — לקרוא ל-AI, לכתוב ל-D1, לשמור ב-R2 — אתה יכול לעשות על מייל נכנס. זה לא "ניתוב mail", זה compute על mail.

מתי לא להשתמש בזה

Email Routing הוא לקבלה. אם הצורך שלך הוא לשלוח עדכונים יזומים למשתמשים — newsletter, התראות push-by-email — זה לא הכלי, וזה גם לא חינמי (נגיע לזה מיד). וגם לקבלה: ה-handler רץ בתוך אותו מודל isolate של Workers, אז עיבוד כבד מאוד על raw גדול עדיין כפוף לאותם שיקולי CPU מפרק 2.

עשו עכשיו 4 דקות

ב-dashboard → Email → Email Routing ודא שיש לך לפחות destination address אחד מאומת (תיבת ה-gmail שלך, עם וי ירוק). תצטרך אותו כדי ש-forward() יעבוד — בלי יעד מאומת, ה-forward ייכשל.

מתחילים החלטה קבלה חינם / שליחה בתשלום 11 דקות

מלכודת ה-Email: קבלה חינם מול שליחה בתשלום

זו ההבחנה היחידה בפרק הזה שיכולה לעלות לך כסף בלי שתשים לב, וכמעט כל מדריך מבלבל אותה. תכניס אותה לראש ונקודה:

Email Routing (קבלה)Email Service (שליחה)
מה זה עושהמקבל ומעבד mail נכנסשולח mail יזום החוצה
מחירחינם, unlimited, כל הטייריםWorkers Paid בלבד ($5/חודש)
מכסהללא הגבלה3,000/חודש כלול, ואז $0.35/1,000*
בקודforward() / reply() / setReject()new EmailMessage(...).send()

* המספרים 3,000/חודש ו-$0.35/1,000 הם כפי שמתועד — דף ה-pricing של Email Service החזיר 404 באימות האחרון. אמת מול הדף הרשמי לפני שתסתמך על נפח שליחה.

למה הבלבול מסוכן

שים לב לעדינות: forward() ו-reply() בתוך ה-email() handler הם inbound — חלק מ-Email Routing, חינם. אבל new EmailMessage(...).send() דרך binding של cloudflare:email הוא outbound — Email Service, בתשלום. שתי השורות נראות כמעט זהות בקוד, אבל אחת חינמית והשנייה דורשת Workers Paid. מדריך שמראה לך "לשלוח email מ-Worker בחינם" כמעט תמיד מתכוון ל-reply() על מייל נכנס — לא לשליחה יזומה.

הדרך הבטוחה לזכור את ההבחנה: שאל את עצמך "האם יש מייל נכנס שאני מגיב עליו?". אם כן — reply()/forward(), חינם, כי אתה בתוך זרימה שכבר התחילה במייל נכנס. אם אתה פותח מייל חדש מאפס (משתמש נרשם ואני שולח לו welcome) — זה send(), paid, כי אתה מקור התקשורת. ההבחנה היא "מי התחיל": אם הלקוח התחיל, התגובה חינם; אם אתה התחלת, השליחה עולה.

ולמה ההבחנה הזו שווה כל כך הרבה תשומת לב: זו המלכודת היחידה בפרק שיכולה להפיל פרויקט שלם. אם בנית את כל ה-onboarding flow שלך מסביב ל"נשלח מייל אימות" והנחת שזה חינם — תגלה ביום ההשקה שאף מייל לא יוצא, או שאתה צריך פתאום $5 שלא תכננת. עדיף לדעת את זה עכשיו ולתכנן את ה-flow בהתאם: או ש-Access מטפל ב-OTP בשבילך (חינם), או שאתה מתקצב Workers Paid מראש.

מה אפשר לבנות ב-$0

בלי Workers Paid אתה יכול: לקבל mail, לעבד אותו (לסווג, לשמור ב-D1, להפעיל AI), להעביר (forward) ליעד מאומת, ולענות אוטומטית (reply) למי ששלח. מה שאתה לא יכול: לפתוח email יזום — welcome mail, התראות, OTP משלך — בלי לשלם.

דוגמה מייצגת — שליחה (Workers Paid בלבד)
// Email Service / send_email binding — Workers PAID
import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';

export default {
  async fetch(request, env) {
    const msg = createMimeMessage();
    msg.setSender({ name: 'My App', addr: 'noreply@example.com' }); // הדומיין חייב Email Routing פעיל
    msg.setRecipient('user@example.com');
    msg.setSubject('Welcome');
    msg.addMessage({ contentType: 'text/plain', data: 'תודה שנרשמת!' });

    const message = new EmailMessage('noreply@example.com', 'user@example.com', msg.asRaw());
    await env.SEB.send(message); // SEB = שם ה-[[send_email]] binding
    return new Response('sent');
  },
};
עשו עכשיו 3 דקות

כתוב לעצמך בשורה אחת: באפליקציה שאתה מתכנן — האם אתה צריך לשלוח email יזום (paid) או רק לעבד/לענות לנכנס (free)? סמן את התשובה בבירור. אם התשובה היא "רק לקבל" — אתה נשאר ב-$0.

טעות נפוצה: לבנות flow ששולח email על ה-free tier

למה זה מפתה: "Email מ-Cloudflare זה חינם" — קראת את זה בעשרה מקומות, ו-forward()/reply() באמת חינמיים, אז למה שגם שליחה לא תהיה?

למה זה טעות: Email Service (שליחה) הוא Workers Paid בלבד — אפס emails/חודש ב-free tier. רק Email Routing (קבלה) חינם. forward()/reply() הם inbound; new EmailMessage().send() הוא paid. מדריכים מערבבים את השניים, ותגלה את זה רק כשה-welcome mail לא יוצא.

מה לעשות במקום: ב-$0 בנה קבלה + auto-reply דרך Email Routing בלבד. אם אתה חייב שליחה יזומה — תכנן ל-$5 Workers Paid (3,000/חודש כלול, $0.35/1,000 אחרי, כמתועד — אמת לפני volume גבוה).

מסגרת החלטה: Email Routing (קבלה, חינם) מול Email Service (שליחה, בתשלום)

שאלה 1 — מה הזרימה צורכת? אם רק קבלה / עיבוד / forward / auto-reply של mail נכנס → Email Routing, $0 unlimited.

שאלה 2 — צריך לשלוח mail יזום? welcome, התראות, OTP משלך → Email Service, Workers Paid ($5/חודש, 3,000/חודש כלול ואז $0.35/1,000 כמתועד).

שאלה 3 — אפשר להחליף שליחה בקבלה? לעיתים כן: במקום לשלוח OTP יזום, גדר ב-Access (שמטפל ב-OTP בעצמו) או השתמש ב-magic link על דומיין שלך.

כלל ברזל: אל תבנה flow ב-free tier שתלוי בשליחת email. אם הוא תלוי — זה כבר תקציב $5, תכנן אותו ככה מראש.

בינוני מעשי $0 (unbilled beta) 14 דקות

Analytics Engine — observability ב-$0 כתחליף ל-Google Analytics

החור האחרון ב-glue: אתה לא יודע מה קורה בפנים. כמה אנשים נכנסו? אילו endpoints נקראים? מאיפה הגיע ה-email? במקום להדביק Google Analytics (cookies, consent banner, scripts של צד-שלישי), Cloudflare נותן לך Analytics Engine — time-series DB בתוך Workers. אתה כותב events מהקוד, ושואל אותם ב-SQL. אין cookies, אין consent, הכל first-party ב-edge.

ההבדל המנטלי מ-Google Analytics: GA הוא client-side — script בדפדפן שמדווח מה המשתמש עשה, ולכן תלוי ב-cookies, נחסם על ידי ad-blockers, ודורש consent banner ב-GDPR. Analytics Engine הוא server-side — אתה רושם event מתוך הקוד שלך, על אירוע שאתה כבר יודע שקרה (בקשה הגיעה, מייל התקבל, פעולה הצליחה). אין מה לחסום ואין מה לבקש עליו הסכמה כי לא נשמר מידע אישי בדפדפן. זה גם הופך אותו למתאים לדברים ש-GA בכלל לא רואה: events מ-cron jobs, מ-email handlers, או מ-API שאין לו UI.

מתי זה לא מתאים: Analytics Engine הוא aggregation — מצוין לספור, לקבץ ולממצע ("כמה page views לכל path", "ממוצע גודל מייל נכנס"). הוא לא תחליף ל-D1 לשמירת רשומות שאתה צריך לקרוא בודדות במדויק — הוא משתמש ב-sampling בקנה מידה גדול. אם אתה צריך "תראה לי בדיוק את ה-event הזה" — זה D1. אם אתה צריך "תראה לי מגמה" — זה Analytics Engine.

המגבלות

ה-binding וה-writeDataPoint

מצרפים dataset ב-wrangler.toml, ואז כותבים מהקוד. הדבר הכי חשוב לזכור: writeDataPoint() הוא fire-and-forget — אסור ל-await. הוא חוזר מיד וה-runtime כותב ברקע.

דוגמה מייצגת — binding + כתיבת event
# wrangler.toml
[[analytics_engine_datasets]]
binding = "AE"
dataset = "my_app_events"
// כתיבת event מותאם (fire-and-forget — בלי await!)
export default {
  async fetch(request, env) {
    env.AE.writeDataPoint({
      blobs:   ['page_view', '/pricing', 'US'], // מימדי string (filterable)
      doubles: [1, 42.5],                        // מדדים מספריים
      indexes: ['user_123'],                     // בדיוק index אחד מותר
    });
    return new Response('logged');
  },
};

שלושת השדות: blobs הם מימדי string (מה אפשר לסנן/לקבץ לפיהם), doubles הם המדדים המספריים (מה אפשר לסכום/לממצע), ו-indexes הוא מפתח ה-sampling — בדיוק אחד מותר ל-data point.

שאילתה ב-SQL API

לקריאה שולחים POST ל-endpoint ה-SQL. שתי מלכודות שתופסות כמעט כל אחד:

דוגמה מייצגת — שאילתה דרך SQL API
POST https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql
Authorization: Bearer <API_TOKEN>   # Account Analytics Read

# גוף הבקשה — raw text, לא JSON:
SELECT blob1 AS path, sum(double1) AS views
FROM my_app_events
GROUP BY path

השם של ה-dataset הוא ה-FROM table; העמודות נקראות blob1, double1, index1 וכו' לפי הסדר שכתבת. יש גם עמודות מובנות של timestamp ו-sampling.

כלל מעשי לתכנון schema של data point: שים ב-blobs את מה שתרצה לסנן ולקבץ לפיו (path, country, event type, sender) ושמור על סדר עקבי — blobs[0] תמיד אותו דבר. שים ב-doubles את מה שתרצה לסכום או לממצע (count, duration, size). וה-index היחיד הוא מפתח ה-sampling — בחר משהו עם cardinality גבוה (כמו user id) כדי שה-sampling יהיה מייצג. שינוי ה-schema אחרי שכבר כתבת נתונים מסבך שאילתות, אז שווה לחשוב על הסדר פעם אחת מראש.

ולמה ה-fire-and-forget הזה (בלי await) הוא דווקא יתרון ולא ויתור: הוא אומר שרישום event לא מאט את הבקשה למשתמש. ה-Worker מחזיר את התשובה מיד, וה-runtime כותב את ה-data point ברקע על חשבונו. בניגוד לכתיבה ל-D1 (שכן צריך await וכן סופר לך CPU/זמן), observability כאן היא כמעט חינמית בביצועים — בדיוק מה שאתה רוצה ממדידה: שלא תשלם עליה בחוויית המשתמש.

עשו עכשיו 3 דקות

הוסף ל-wrangler.toml בלוק [[analytics_engine_datasets]] עם binding = "AE" ו-dataset = "my_app_events". שמור — אל תפרוס עדיין. בסעיף הבא נחבר אותו לזרימת ה-email.

בינוני קונספט $0 (20 secrets) 9 דקות

Secrets Store — ניהול secrets ברמת חשבון (beta)

עד עכשיו כל secret נכנס דרך wrangler secret put — והוא נשמר פר-Worker. זה מצוין כשה-secret שייך ל-Worker אחד. אבל מה אם אותו TURNSTILE_SECRET_KEY צריך לשרת שלושה Workers שונים? עם secret put תכפיל אותו שלוש פעמים, ובכל סיבוב צריך לעדכן את כולם.

Secrets Store (beta) פותר את זה: secrets ברמת החשבון, שמשותפים בין Workers בלי כפילות. מגדירים את ה-secret פעם אחת, ומחברים אליו כל Worker שצריך. כשמסובבים את ה-secret (rotation) — מעדכנים במקום אחד וכל ה-Workers מקבלים את הערך החדש, במקום לרדוף אחרי כל אחד מהם.

למי זה לא רלוונטי כרגע: אם אתה בונה Worker בודד — וזה רוב הפרויקטים בקורס הזה — wrangler secret put מספיק לחלוטין, והוא פשוט יותר. Secrets Store מצדיק את עצמו רק כשיש לך כמה Workers שחולקים את אותו סוד (מפתח API משותף, secret של Turnstile שמשרת כמה אפליקציות). הכלל: אל תוסיף אותו "ליתר ביטחון" — זו עוד תלות ב-beta שעלולה לקבל מחיר, ועוד role להגדיר. הוסף אותו רק כשהכאב של כפילות secret אמיתי.

דוגמה מייצגת — binding של Secrets Store
# wrangler.toml
[[secrets_store_secrets]]
binding = "TURNSTILE"
store_id = "<STORE_ID>"
secret_name = "TURNSTILE_SECRET_KEY"
// runtime
const secret = await env.TURNSTILE.get();
עשו עכשיו 3 דקות

החלט לפרויקט שלך: האם אותו secret (למשל TURNSTILE_SECRET_KEY) צריך להיות משותף לכמה Workers? אם כן → Secrets Store. אם לא → wrangler secret put מספיק. רשום את ההחלטה — אל תוסיף מורכבות שאתה לא צריך.

מתקדם אינטגרציה $0 12 דקות

חיבור ה-Glue: email-to-AI + analytics בזרימה אחת

עכשיו נחבר כמה חוטים יחד לזרימה אחת שמדגימה את כל מה שבנינו: email-to-AI triage. מייל נכנס → Worker מסווג אותו עם Workers AI (פרק 3) → שומר ב-D1 (פרק 2) → כותב event ל-Analytics Engine. זו תיבת triage חכמה, ב-$0, בלי שום שירות צד-שלישי.

core Worker + D1 + KV + AI Turnstile $0 — ללא מגבלת נפח Access $0 — 50 seats (hard) named tunnel $0 — SSE + ללא תקרה Email קבלה $0 / שליחה paid Analytics Engine 100k/יום · unbilled beta
דוגמה מייצגת — email נכנס → (AI) → D1 → Analytics Engine
export default {
  async email(message, env, ctx) {
    const from = message.from;
    const subject = message.headers.get('subject') || '';

    // (אופציונלי) סיווג עם Workers AI מפרק 3
    // const { response: label } = await env.AI.run('@cf/...', {
    //   prompt: `סווג את הנושא לקטגוריה אחת: ${subject}` });

    // שמירה ב-D1 (פרק 2)
    await env.DB.prepare(
      'INSERT INTO inbound (sender, subject, size) VALUES (?, ?, ?)'
    ).bind(from, subject, message.rawSize).run();

    // event ל-Analytics Engine — בלי await!
    env.AE.writeDataPoint({
      blobs:   ['inbound_email', from, subject],
      doubles: [message.rawSize],
      indexes: [from],
    });

    // forward ליעד מאומת (אופציונלי)
    await message.forward('me@example.com');
  },
};

שים לב כמה מעט קוד זה דורש, וכמה primitives מהפרקים הקודמים נכנסים פנימה: email() מ-Email Routing, AI.run() מפרק 3, DB (D1) מפרק 2, ו-AE.writeDataPoint() מהסעיף הקודם — וכולם ב-$0. זו בדיוק שכבת ה-glue שתיכנס ל-capstone של פרק 6: full-stack מגודר ב-Turnstile + Access, עם observability ב-Analytics Engine.

למה הזרימה הזו היא דוגמה טובה ל-glue בפעולה: היא לא משתמשת בשום שירות חיצוני, אין בה API key של ספק אחר, ואין בה תשלום. מערכת triage כזו ב-stack מסורתי הייתה דורשת שירות לקבלת mail (Mailgun/SendGrid inbound), endpoint לעבד אותו, קריאה ל-OpenAI, DB, ושירות analytics — חמישה חשבונות, חמישה חיובים פוטנציאליים. כאן זה Worker אחד ו-wrangler.toml אחד. זה לא רק זול — זה פחות נקודות כשל ופחות דברים לתחזק, וזה בדיוק היתרון שאתה רוצה כ-vibe coder.

נקודת זהירות אחת על שילובים: אם תוסיף ל-handler הזה גם שליחת תשובה יזומה (לא reply() על הנכנס, אלא מייל חדש למישהו אחר) — חצית את הקו ל-Email Service, וזה כבר Workers Paid. כל עוד אתה רק מקבל, מעבד, שומר, מודד ו-forward/reply — אתה ב-$0.

עשו עכשיו 4 דקות

צייר על נייר את הזרימה ב-5 חצים בלבד: אימייל נכנס → איזה שדה ב-message תשתמש לסיווג → לאיזה מודל Workers AI → איזו טבלת D1 → איזה data point ל-Analytics Engine. זה ה-blueprint של תרגיל 4.

תרגיל: Email Routing → Worker → Analytics Engine 30 דקות
  1. לפני הכל — ב-Dashboard → Email → Email Routing → Destination addresses: ודא שיש לך לפחות destination address אחד עם וי ירוק (Verified). אם קיבלת email אימות ולא לחצת על הקישור — כל forward() ייכשל בשקט; לחץ על הקישור עכשיו.
  2. ב-Email Routing הגדר route של כתובת ל-Worker (לא forward פשוט).
  3. הוסף [[analytics_engine_datasets]] (binding = "AE", dataset = "my_app_events") ל-wrangler.toml.
  4. כתוב email(message, env, ctx): קרא subject/from, החלט סיווג (אופציונלי עם Workers AI מפרק 3).
  5. env.AE.writeDataPoint({ blobs:['inbound_email', from, subject], doubles:[message.rawSize], indexes:[from] })בלי await.
  6. אופציונלי: message.forward() ליעד מאומת, או setReject().
  7. npx wrangler deploy, שלח email לכתובת, ואז הרץ שאילתת SQL API לאמת שה-event נכתב.

פלט נראה לעין: Worker חי שמקבל email נכנס, כותב event ל-Analytics Engine, וניתן לשאול אותו ב-SQL API — triage/analytics ב-$0 בלי צד שלישי.

מתחילים סיכום $0 8 דקות

סיכום — checklist ומפת המגבלות של שכבת ה-Glue

חיברת את כל שכבת ה-glue: הגנת bot, זהות, חשיפה יציבה, email נכנס, ו-observability — חמישה רכיבים שהופכים Worker עירום לאפליקציה אמיתית, מאובטחת ונצפית. כל אחד מהם, חוץ משליחת email, נשאר ב-$0 — וזה לא מקרי: זו בדיוק הנקודה של הפרק. אבל "חינם" אצל Cloudflare תמיד מגיע עם מגבלה ספציפית שנופלת ראשונה, וההבדל בין מי שנכווה למי שלא הוא מי שיודע איזו מגבלה ו-מתי. לפני שנעבור ל-capstone, הנה מפת המגבלות של מה שבנית — היכן כל רכיב נשבר:

רכיבמגבלה חינמיתנשבר ב...אחרי
Turnstileללא מגבלת נפח20 widgets/חשבון (מבני)Enterprise
Zero Trust Access50 seats (hard)המשתמש ה-51$7/user/month
named tunnelחינם, ללא תקרת concurrent
Email Routing (קבלה)unlimited
Email Service (שליחה)0 ב-freeכל שליחה$5 + 3,000/חודש, $0.35/1,000*
Analytics Engine100k writes/יום, unbilledהחיוב המתוכנן יופעל$0.25/M writes + $1/M reads

* מספרי Email Service כמתועד — אמת מול הדף הרשמי לפני volume שליחה.

עשו עכשיו 3 דקות

עבור על ה-checklist בסוף הפרק וסמן אילו רכיבים כבר חיברת בפועל מול אילו רק קראת עליהם. הפער הזה הוא מה שתשלים לפני שתיכנס ל-capstone של פרק 6.

שגרת עבודה — להוסיף glue לכל Worker חדש
מתיפעולהפקודה / מקום
יש טופס/API ציבוריהוסף Turnstile + siteverify בשרתwrangler secret put TURNSTILE_SECRET_KEY
צריך login לקהל סגורגדר ב-Access (אם <50)Access controls → Applications
יש SSE/streamingהחלף Quick ל-named tunnelwrangler tunnel create/run
צריך לקבל emailהוסף email() handlerEmail Routing → route ל-Worker
רוצה לראות מה קורהכתוב events (בלי await)env.AE.writeDataPoint(...)
secret משותף לכמה Workersהעבר ל-Secrets Store[[secrets_store_secrets]]
דבר אחד שכדאי לעשות עכשיו

קח את ה-Worker הציבורי הכי "אמיתי" שלך וחבר לו רק דבר אחד מהפרק: Turnstile עם siteverify בצד שרת על הטופס שלו. זה החיבור עם ה-ROI הכי גבוה — חינם לחלוטין, מגן מיידית מפני בוטים, ולוקח 25 דקות. את שאר ה-glue תוסיף כשתצטרך, אבל הגנת bot על endpoint ציבורי היא לא אופציה — היא בסיס.

בדוק את עצמך
  1. למה אימות Turnstile חייב לקרות בצד שרת (siteverify) ולא מספיק ה-callback בצד הלקוח?
  2. למה Quick Tunnel נכשל ב-SSE, ומה ה-named tunnel עושה אחרת שמאפשר streaming?
  3. מתי email דורש תשלום ומתי הוא חינם — ואיזו שורת קוד מבדילה בין השניים?
  4. כיצד מחליטים אם לגדר כלי ב-Zero Trust Access, ומה קורה כשהמשתמש ה-51 מנסה להיכנס?
  5. למה אסור ל-await את writeDataPoint(), וכמה indexes מותר ל-data point אחד?
סיכום הפרק והגשר לפרק 6

הוספת את חמשת רכיבי ה-glue שהופכים Worker לאפליקציה: Turnstile (הגנת bot חינמית עם אימות בצד שרת), Zero Trust Access (login בלי קוד auth, 50 seats), named tunnel (חשיפה יציבה עם SSE), Email Routing (קבלת mail בחינם — להבדיל משליחה בתשלום), ו-Analytics Engine (observability ב-$0 בלי cookies). למדת את ההבחנה הקריטית — כל ה-glue חינמי חוץ משליחת email, ש-Workers Paid — ואת ה-gotchas: siteverify בשרת בלבד, writeDataPoint בלי await ו-index יחיד, ו-named tunnel ל-SSE.

הפרק הבא (פרק 6 — Full-Stack חינמי): נחבר את הכל ל-SaaS חי full-stack — Worker + Static Assets + D1 + R2 + KV + Workers AI + Vectorize RAG, מגודר ב-Turnstile ו-Access עם observability ב-Analytics Engine — ונבנה את מטריצת ה-pay-or-not שממפה לכל primitive את המגבלה שנופלת ראשונה ומתי ה-$5 הופך משתלם. שכבת ה-glue שבנית פה נכנסת לשם ישירות.

צ'קליסט — סיכום פרק 5