תוצאות נוספות...

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
× Send

אוטומציה שרצה לבד: המדריך המלא לטריגרים ב-Google Apps Script

אוטומציה שרצה לבד: המדריך המלא לטריגרים ב-Google Apps Script
תוכן עניינים

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

 

הישארו מעודכנים

רוצים לקבל עדכונים בלייב? רוצים מקום בו אתם יכולים להתייעץ עם מומחי AI, לשאול שאלות ולקבל תשובות? רוצים לשמוע על מבצעים והטבות לכלי ה-AI שמשנים את העולם? הצטרפו לקהילות ה-AI שלנו.

אפשר גם להרשם לניוזלטר שלנו

למה הריצה הידנית היא לא אוטומציה

במדריך הקודם על Google Apps Script בנינו סקריפט שקורא טבלה ושולח תזכורות, אבל לא הסברנו לעומק מה זה טריגר, טריגר הוא הגדרה שאומרת לגוגל מתי להריץ פונקציה מסוימת בפרויקט שלכם. הוא לא חלק מהקוד, הוא יושב לצידו, ואפשר להוסיף ולהסיר אותו בלי לגעת בשורה אחת של קוד. זו גם הסיבה שקל לשכוח שהוא קיים.

 

המדריך הזה עומד בפני עצמו ולא דורש את הקודם, במדריך הקודם הסברנו מה זה Apps Script, מה המגבלות שלו, ואיך מודל שפה כותב את כל הקוד במקומכם. כאן נתמקד במה שקורה אחרי שהקוד כבר עובד.

 

מה נבנה כאן?

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

 

שימו לב שזו משימה שאין שום היגיון להריץ ידנית. אף אחד לא ייכנס כל בוקר לעורך קוד וילחץ Run כדי לשמור שלושה קבצים. בלי טריגר, האוטומציה הזו חסרת ערך לחלוטין.

 

מה אתם צריכים בשביל להתחיל

  • חשבון גוגל כלשהו, פרטי או עסקי
  • תווית אחת בג'ימייל עם כמה מיילים שיש בהם קבצים מצורפים
  • תיקייה אחת בגוגל דרייב
  • גישה לקלוד או למודל שפה אחר
  • כרבע שעה

 

שלב 1: מכינים את התווית ואת התיקייה

1. יוצרים תווית בג'ימייל: בדוגמה שלנו קראנו לה "חשבוניות" ותייגנו בה שלושה מיילים, בכל אחד קובץ PDF מצורף. התווית היא הדבר היחיד שתמשיכו לעשות ידנית מכאן והלאה, ולכן כדאי שתהיה קצרה וקלה לזכירה. שם התווית חייב להיות מדויק, כי הסקריפט מחפש בדיוק את המחרוזת הזו.

 

תווית חשבוניות בג'ימייל עם שלושה מיילים שיש בהם קבצי PDF מצורפים

תווית חשבוניות בג'ימייל עם שלושה מיילים שיש בהם קבצי PDF מצורפים

 

2. יוצרים תיקייה בדרייב ומעתיקים את המזהה שלה: הסקריפט לא יודע לחפש תיקייה לפי שם, הוא צריך את המזהה הייחודי שלה. הדרך הקלה להשיג אותו היא ללחוץ על שם התיקייה, לבחור Share ואז Copy link, ולהדביק את הקישור איפשהו. המזהה הוא הרצף הארוך של האותיות והמספרים שמופיע בסוף הכתובת, אחרי /folders/.

 

תפריט תיקייה בגוגל דרייב פתוח עם האפשרות Share ואז Copy link

Share ואז Copy link. המזהה של התיקייה מסתתר בתוך הכתובת שמתקבלת

 

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

 

שלב 2: פותחים פרויקט עצמאי

3. נכנסים ל-script.google.com ולוחצים New project: כאן יש הבדל מהמדריך הקודם, וכדאי לשים לב אליו. שם נכנסנו לעורך דרך התפריט Extensions בתוך הגיליון, כי הסקריפט היה צמוד לקובץ מסוים. הפעם אין קובץ אחד שהסקריפט שייך אליו, הוא עובד מול ג'ימייל ומול דרייב, ולכן פותחים אותו כפרויקט עצמאי.

 

מסך My Projects של Apps Script עם הכפתור New project

פרויקט עצמאי, כי הסקריפט הזה לא שייך לקובץ מסוים

 

שלב 3: מבקשים מהמודל את הקוד

4. מנסחים פרומפט שמתאר מבנה ולא רק רצון: זה אותו עיקרון מהמדריך הקודם. המודל כותב Apps Script ברמה גבוהה, אבל הוא לא רואה את התיבה שלכם ולא את הדרייב שלכם. שם התווית המדויק, מזהה התיקייה ופורמט תת-התיקיות הם מה שמפריד בין קוד שעובד לקוד שנראה תקין אבל פונה למקום הלא נכון.

 

זה הפרומפט המלא שהשתמשתי בו (לא לשכוח לשנות את ה-ID של התיקייה שלכם):

 

אני עובד ב-Google Apps Script, בפרויקט עצמאי שנוצר דרך
script.google.com (לא מחובר לקובץ מסוים).

מה שקיים אצלי:
- תווית בג'ימייל בשם "חשבוניות" שאליה מגיעים מיילים עם קבצים
  מצורפים (בעיקר PDF)
- תיקייה בגוגל דרייב שה-ID שלה הוא FOLDER_ID_HERE

מה שאני צריך שהסקריפט יעשה בכל הרצה:
1. לחפש בג'ימייל שיחות שיש להן את התווית "חשבוניות" ואין להן
   את התווית "נשמר".
2. לעבור על ההודעות בכל שיחה ולקחת מהן את הקבצים המצורפים.
3. להתעלם מתמונות שמוטמעות בגוף המייל (חתימות ולוגואים),
   ובנוסף לדלג על כל קובץ שקטן מ-20KB.
4. בתוך התיקייה שנתתי, ליצור תת-תיקייה לפי חודש בפורמט
   YYYY-MM לפי תאריך המייל, וליצור אותה רק אם היא לא קיימת.
5. לשמור את הקובץ לתת-התיקייה של אותו חודש. אם כבר קיים שם
   קובץ באותו שם, להוסיף לשם מספר רץ כדי לא לדרוס.
6. רק אחרי ששמירת כל הקבצים של אותה שיחה הצליחה, להוסיף לשיחה
   את התווית "נשמר". אם התווית "נשמר" לא קיימת בחשבון, שהסקריפט
   ייצור אותה בעצמו.
7. אם לא נמצאה אף שיחה חדשה, לא לעשות כלום ולסיים בשקט.

דרישות נוספות:
- אם התווית "חשבוניות" לא קיימת בחשבון, לזרוק שגיאה עם הודעה
  ברורה באנגלית ולא לסיים בשקט. חשוב לי שההרצה תיכשל במקרה הזה.
- לטפל בכל שיחה בתוך try/catch נפרד, כך ששיחה בעייתית אחת
  לא תעצור את כל ההרצה. במקרה כזה לרשום לוג ולהמשיך לשיחה הבאה,
  ובלי לסמן "נשמר".
- להגביל כל הרצה ל-50 שיחות לכל היותר, כדי לא להתקרב למגבלת
  שש הדקות. אם היו יותר, לרשום את זה בלוג.
- אזור הזמן Asia/Jerusalem.
- Logger.log ברור: כמה שיחות נמצאו, כמה קבצים נשמרו, כמה קבצים
  דולגו ולמה, וכמה שיחות נכשלו.
- שם הפונקציה וההערות בקוד באנגלית.

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

 

שלושה סעיפים כאן קיימים רק בגלל הטריגר שיבוא אחר כך, וכדאי להבין למה:

  1. הסעיף על התווית "נשמר" הוא המקבילה המדויקת לעמודת הסטטוס מהמדריך הקודם. בלעדיו, הסקריפט ישמור כל בוקר מחדש את אותן חשבוניות, ותוך שבוע תהיה לכם תיקייה עם שבעה עותקים של כל קובץ.
  2. הסעיף על התקרה של 50 שיחות בהרצה קיים כי פונקציה ב-Apps Script נעצרת אחרי שש דקות. כשמריצים ידנית פעם אחת על שלושה מיילים זה לא רלוונטי. כשטריגר רץ על תיבה שהצטברו בה מאתיים מיילים לא מטופלים, זה כן.
  3. והסעיף שדורש שהסקריפט יתריע לנו אם התווית לא קיימת הוא זה שנשתמש בו בסוף המדריך כדי לשבור את האוטומציה בכוונה ולראות מה קורה.

 

שלב 4: מדביקים, מגדירים אזור זמן, ומריצים ידנית

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

 

עורך Apps Script עם הקוד שהודבק ושם הפרויקט שמירת חשבוניות

עורך Apps Script עם הקוד שהודבק ושם הפרויקט שמירת חשבוניות

 

6. בודקים את אזור הזמן בהגדרות הפרויקט: לוחצים על אייקון גלגל השיניים בסרגל הצד ומוודאים ש-Time zone מוגדר לישראל. השדה הזה כמעט תמיד מוגדר נכון מלכתחילה, ורוב האנשים לא פותחים אותו אף פעם, אבל הוא זה שקובע לפי איזו שעה ירוץ הטריגר שנגדיר בשלב הבא. אם הוא מוגדר לאזור זמן אחר, הטריגר "של אחת בלילה" ירוץ בשעה אחרת לגמרי.

 

מסך Project settings של Apps Script עם אזור הזמן מוגדר לזמן ישראל

ההגדרה שקובעת לפי איזו שעה ירוץ הטריגר. שווה מבט אחד לפני שממשיכים

 

7. מריצים ידנית ומאשרים הרשאות: בוחרים בסרגל העליון את הפונקציה, כאן saveInvoiceAttachmentsToDrive, ולוחצים Run. גוגל תעצור אתכם ותבקש אישור, ואחרי Review permissions ובחירת החשבון יופיע מסך שמפרט בדיוק מה הסקריפט מבקש.

 

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

 

מסך אישור ההרשאות של גוגל שמפרט גישה מלאה לג'ימייל ולדרייב

זה מה שאתם מאשרים בפועל. שווה לקרוא לפני שלוחצים Allow

 

בדרך תעברו גם במסך אדום שמודיע ש-Google hasn't verified this app, וממשיכים ממנו דרך Advanced. הסברנו את זה בהרחבה במדריך הקודם, ובקצרה: גוגל מאמתת רק אפליקציות שפורסמו לציבור, וסקריפט אישי שכתבתם לעצמכם לפני שתי דקות מעולם לא עבר אימות כזה ולא אמור לעבור.

 

שלב 5: בודקים שההרצה הידנית עבדה

8. קוראים את ה-Execution log: בתחתית המסך נפתח חלון שמדווח בדיוק מה קרה. במקרה שלנו הוא מספר שהתווית "נשמר" נוצרה אוטומטית, שנמצאו שלוש שיחות, שנוצרה תת-תיקייה בשם 2026-08, ושכל שלושת הקבצים נשמרו. בסוף מופיע סיכום מסודר עם המספרים.

 

חלון Execution log של Apps Script עם סיכום מוצלח של שלוש שיחות ושלושה קבצים שנשמרו

חלון Execution log של Apps Script עם סיכום מוצלח של שלוש שיחות ושלושה קבצים שנשמרו

 

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

 

9. מוודאים בדרייב: בתיקייה שהגדרנו נוצרה תת-תיקייה 2026-08, ובתוכה שלושת קבצי ה-PDF. החלוקה לפי חודש נעשית לפי תאריך המייל ולא לפי תאריך ההרצה, כך שמייל ישן שתיוג היום ייכנס לתיקייה הנכונה.

 

תיקיית דרייב עם תת-תיקייה בשם 2026-08 שנוצרה אוטומטית

תת-התיקייה נוצרה לבד, בפורמט שמסתדר לפי סדר כרונולוגי

 

שלושה קבצי PDF של חשבוניות בתוך תיקיית 2026-08 בגוגל דרייב

שלושה קבצי PDF של חשבוניות בתוך תיקיית 2026-08 בגוגל דרייב

 

שלב 6: מוסיפים את הטריגר

עד כאן בנינו כלי שרץ כשלוחצים עליו. השלב הבא הוא מה שהופך אותו לתהליך אוטומטי.

 

10. נכנסים למסך Triggers: בסרגל הצד השמאלי של העורך יש אייקון שעון מעורר. במסך שנפתח לוחצים על Add Trigger בפינה.

 

מסך Triggers ריק ב-Apps Script עם הכפתור Add Trigger

מסך Triggers ריק ב-Apps Script עם הכפתור Add Trigger

 

11. ממלאים את חלון ההגדרה: זה החלון החשוב ביותר במדריך:

  • Choose which function to run: הפונקציה הראשית, במקרה שלנו saveInvoiceAttachmentsToDrive. שימו לב שהתפריט מציג את כל הפונקציות שבקובץ, כולל פונקציות עזר פנימיות, ובחירה שגויה כאן תיצור טריגר שרץ ולא עושה כלום.
  • Choose which deployment should run: משאירים Head, כלומר תריץ תמיד את הגרסה האחרונה שנשמרה בעורך. זה מה שרוצים בסקריפט אישי.
  • Select event source: Time-driven, כלומר לפי שעון.
  • Select type of time-based trigger: כאן בחרנו Day timer.
  • Select time of day: טווח של שעה, כאן בין אחת לשתיים בלילה. אין עומס על תיבת המייל בשעה הזו, ורוב החשבוניות של אתמול כבר הגיעו.

 

חלון Add Trigger מוגדר עם Day timer בין אחת לשתיים בלילה והתראת כשל מיידית

ההגדרה המלאה. שימו לב ל-GMT+03:00 מתחת לשעה, שמגיע מהגדרות הפרויקט

 

שורת ה-GMT+03:00 שמופיעה מתחת לבחירת השעה היא בדיוק אזור הזמן שראינו בהגדרות הפרויקט. גוגל מזכירה לכם כאן לפי איזה שעון היא הולכת לעבוד.

 

12. מבינים מה עוד היה שם: כשפותחים את התפריט של סוג הטריגר מתגלה שיש שש אפשרויות, וכל אחת מתאימה למשהו אחר.

 

תפריט סוגי הטריגרים המבוססי זמן פתוח עם שש אפשרויות

שש אפשרויות תזמון. רוב האנשים לא פותחים את התפריט הזה אף פעם

 

  • Specific date and time להרצה חד-פעמית בתאריך ידוע. שימושי לתזכורת לאירוע מסוים.
  • Minutes timer כל דקה, חמש, עשר, חמש עשרה או שלושים. מפתה, ומסוכן: הרצה כל דקה פירושה 1,440 הרצות ביום שרובן המוחלט לא ימצאו כלום. שמרו את זה לבדיקות בלבד, כמו שנעשה בעצמנו בשלב הבא.
  • Hour timer כל שעה עד כל שתים עשרה שעות. מתאים לתהליכים שבאמת צריכים תגובה תוך כמה שעות.
  • Day timer פעם ביום בטווח שעות שבוחרים. ברירת המחדל ההגיונית לרוב האוטומציות האישיות.
  • Week timer ביום קבוע בשבוע. מצוין לדוחות סיכום.
  • Month timer בתאריך קבוע בחודש. מתאים לתהליכי סוף או תחילת חודש.

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

 

שלב 7: ההגדרה שאף אחד לא נוגע בה וחבל

בצד ימין של אותו חלון יושב שדה בשם Failure notification settings, וברירת המחדל שלו היא Notify me daily. זו כנראה ההגדרה החשובה ביותר בכל המסך, והיא היחידה שקובעת אם תדעו בכלל שהאוטומציה שלכם נכשלה.

 

13. משנים ל-Notify me immediately: ההבדל הוא בין לגלות שהתהליך נפל תוך דקה לבין לגלות את זה למחרת. בתהליך שרץ פעם ביום, סיכום יומי אומר שיום שלם עובר בין הכשל להתראה. כשמדובר בחשבוניות אפשר לחיות עם זה, וכשמדובר בתהליך שאתם באמת נשענים עליו, פחות.

 

14. שומרים ובודקים שהטריגר נוצר: אחרי Save מופיעה שורה במסך Triggers עם הפונקציה, סוג האירוע, וגם עמודה בשם Error rate. כרגע היא ריקה. תזכרו אותה, כי בעוד רגע היא תתמלא.

 

רשימת הטריגרים ב-Apps Script עם טריגר אחד פעיל ועמודת Error rate ריקה

הטריגר קיים. מכאן והלאה הסקריפט רץ בלעדיכם

 

מהרגע הזה האוטומציה חיה. אתם מתייגים מייל בתווית, ובלילה הקבצים כבר יושבים במקום הנכון בדרייב.

 

שלב 8: בואו נשבור את זה בכוונה

כאן מגיע החלק שמפריד בין מי שיש לו אוטומציה למי שיש לו אוטומציה שהוא יכול לסמוך עליה.

 

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

 

15. מאיצים זמנית את הטריגר: עורכים את הטריגר ומשנים אותו ל-Minutes timer כל חמש דקות. זה טריק שימושי לכל בדיקה של תהליך מתוזמן, ואין שום סיבה לחכות עד מחר בלילה כדי לראות אם משהו עובד.

 

16. גורמים לתקלה אמיתית: נכנסים לג'ימייל ומשנים את שם התווית מ"חשבוניות" ל"חשבוניות2". לא נגענו בקוד, לא נגענו בטריגר. זו בדיוק התקלה הכי נפוצה באוטומציות מהסוג הזה: מישהו מסדר את התוויות, משנה שם תיקייה, או מוחק עמודה, בלי לחשוב על הסקריפט שרץ ברקע.

 

17. ההתראה מגיעה: תוך דקות נכנס לתיבה מייל מ-noreply-apps-scripts-notifications@google.com עם שורת הנושא Summary of failures ושם הפרויקט. בתוכו טבלה עם שעת הכשל, שם הפונקציה, סוג הטריגר, והודעת השגיאה המלאה.

 

 מייל התראת כשל מגוגל אפס סקריפט עם טבלה שמפרטת את שגיאת התווית שלא נמצאה

מייל התראת כשל מגוגל אפס סקריפט עם טבלה שמפרטת את שגיאת התווית שלא נמצאה

 

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

 

18. מסתכלים במסך Executions: אייקון הרשימה בסרגל הצד מוביל למסך שמציג את כל ההרצות. כאן רואים את התמונה המלאה: הרצה אחת מסוג Editor עם סטטוס Completed, זו הידנית שלנו מקודם, ומתחתיה שתי הרצות מסוג Time-driven עם סטטוס Failed באדום. לחיצה על שורה פותחת את הלוג עם השגיאה המדויקת ואפילו את מספר השורה בקוד.

 

מסך Executions של Apps Script עם הרצה מוצלחת מהעורך ושתי הרצות כושלות מהטריגר

העמודה Type מבדילה בין מה שהרצתם ידנית לבין מה שהטריגר הריץ

 

העמודה Type היא הפרט המועיל ביותר במסך הזה. היא מבדילה בין הרצות שאתם יזמתם לבין הרצות שהטריגר יזם, וזה בדיוק המידע שאתם צריכים כשמשהו עובד אצלכם בעורך ולא עובד בלילה.

 

19. מתקנים ומחזירים לשגרה: מחזירים את שם התווית, מוודאים שההרצה הבאה מסתיימת ב-Completed, ומחזירים את הטריגר ל-Day timer. אל תשכחו את השלב האחרון הזה, כי טריגר שנשאר על כל חמש דקות ימשיך לרוץ 288 פעמים ביום.

 

ארבעה דברים שלא רואים במסך

  1. טריגרים שייכים לחשבון, לא לקובץ - אם תעתיקו את הפרויקט או תשתפו אותו, הטריגרים לא יעברו איתו. מי שמקבל את הסקריפט מקבל קוד בלבד, וצריך להגדיר לעצמו טריגר משלו שירוץ בהרשאות שלו.
  2. אין שום מנגנון שמונע כפילויות - אם תלחצו Add Trigger פעמיים על אותה פונקציה, גוגל תיצור שני טריגרים ולא תגיד מילה. בסקריפט שלנו זה יגרום לשתי הרצות מקבילות שרבות על אותם מיילים. כשמשהו מתנהג מוזר, המסך הראשון לבדוק הוא רשימת הטריגרים.
  3. יש הבדל בין טריגר מותקן לטריגר פשוט - פונקציות כמו onEdit או onOpen רצות אוטומטית בלי שתגדירו כלום, אבל הן חיות בהרשאות מצומצמות ולא יכולות לשלוח מיילים או לגשת לשירותים אחרים. כשמישהו מספר לכם ש-onEdit שלו לא שולח מייל, זו הסיבה, והפתרון הוא להגדיר את אותה פונקציה כטריגר מותקן דרך אותו מסך Add Trigger.
  4. לזמן הריצה יש תקרה יומית - בחשבון פרטי מדובר בכתשעים דקות ביום לכל הסקריפטים יחד, ובחשבון Workspace בכשש שעות. זה נשמע הרבה, ומספיק בקלות לתהליכים סבירים, אבל טריגר שרץ כל דקה על משימה כבדה יכול לשרוף את זה. בנוסף, מספר הטריגרים שאפשר להגדיר לסקריפט בודד מוגבל אף הוא. המספרים המדויקים מתעדכנים מדי פעם ומתפרסמים בדף המכסות הרשמי של גוגל.

 

טיפים שיחסכו לכם זמן

  • בדקו תהליך מתוזמן עם Minutes timer, ואל תחכו למחר בבוקר כדי לגלות שהוא נופל. רק זכרו להחזיר את ההגדרה אחר כך.
  • הריצו ידנית לפחות פעם אחת לפני שמגדירים טריגר. ההרצה הידנית היא זו שמציגה את מסך ההרשאות. בלעדיה הטריגר הראשון ייכשל על אישור שלא ניתן, וזה כשל מבלבל במיוחד.
  • בקשו מהמודל לוג מפורט וסיכום מספרי כבר בפרומפט הראשון. הלוג הוא כל מה שיש לכם כשאתם לא נמצאים מול המסך בזמן ההרצה.
  • בקשו שכשל יהיה "רועש". סקריפט שמסיים בשקט כשמשהו חסר הוא סקריפט שתגלו שנשבר רק בעוד חודשיים.
  • כשמגיעה שגיאה, אל תנחשו. העתיקו את הודעת השגיאה המלאה מ-Executions והדביקו אותה בחזרה לאותה שיחה עם המודל שבה קיבלתם את הקוד.

 

שתי אזהרות לפני שאתם מגדירים טריגר ראשון

  1. טריגר מריץ קוד בהרשאות שלכם גם כשאתם ישנים - במסך ההרשאות ראינו במפורש גישה מלאה לג'ימייל ולדרייב, כולל מחיקה. כשמדובר בקוד שייצרתם בעצמכם דרך מודל שפה, לצורך שאתם מבינים, זה בסדר גמור. כשמדובר בקוד שהגיע ממקור לא מוכר, זכרו שטריגר הופך אותו למשהו שירוץ שוב ושוב בלי שאף אחד יסתכל. אם אתם לא בטוחים מה קוד עושה, הדביקו אותו למודל ובקשו הסבר בעברית פשוטה לפני שאתם מגדירים לו תזמון.
  2. התחילו מתהליכים שלא הורסים כלום - אוטומציה שקוראת, שומרת ומתייגת היא בטוחה. אוטומציה שמוחקת מיילים או קבצים לפי כלל אוטומטי היא סיפור אחר, ובגרסה המתוזמנת שלה היא יכולה למחוק הרבה מאוד דברים לפני שתשימו לב. אם בכל זאת בונים תהליך כזה, כדאי שהוא יעביר לארכיון או לתיקייה זמנית במקום למחוק.

 

מה לתזמן בשבוע הקרוב

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

 

השאלה שכדאי לשאול את עצמכם היא לא "מה אפשר לבנות", אלא "איזו פעולה קטנה אני עושה שוב ושוב באותו יום בשבוע". זו המועמדת הראשונה לטריגר, וכנראה שכבר יש לכם אחת בראש.

 

ואם אתם רוצים להעמיק בתהליכים שרצים לבד, אפשר להמשיך מכאן לסוכנים מנוהלים של קלוד ולהפעלת משימות מרחוק, ומי שרוצה ללמוד את זה בצורה מסודרת מוזמן לקורס קלוד קוד של Let's AI, שמיועד גם למי שאין לו רקע טכני.

 

הקוד המלא

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

 

/**
 * Saves attachments from Gmail threads labeled "חשבוניות" into monthly
 * sub-folders (YYYY-MM) inside a given Drive folder, then marks the thread
 * with the "נשמר" label.
 *
 * Run manually from the editor: saveInvoiceAttachmentsToDrive
 */

// ===== Configuration =====
var ROOT_FOLDER_ID       = 'YOUR_FOLDER_ID_HERE';
var SOURCE_LABEL_NAME    = 'חשבוניות';
var DONE_LABEL_NAME      = 'נשמר';
var MIN_FILE_SIZE_BYTES  = 20 * 1024; // 20KB - anything smaller is skipped
var MAX_THREADS_PER_RUN  = 50;        // keeps a single run away from the 6-minute limit
var TIME_ZONE            = 'Asia/Jerusalem';


/**
 * Main entry point. Run this function manually from the editor.
 */
function saveInvoiceAttachmentsToDrive() {

  // The source label must exist. If it does not, fail loudly.
  var sourceLabel = GmailApp.getUserLabelByName(SOURCE_LABEL_NAME);
  if (!sourceLabel) {
    throw new Error(
      'Gmail label "' + SOURCE_LABEL_NAME + '" was not found in this account. ' +
      'Create the label in Gmail with this exact name (no extra spaces) and run again.'
    );
  }

  // Threads that carry the source label but were not processed yet.
  var query = 'label:"' + SOURCE_LABEL_NAME + '" -label:"' + DONE_LABEL_NAME + '"';

  // Fetch one extra thread so we can tell whether there is a backlog.
  var threads = GmailApp.search(query, 0, MAX_THREADS_PER_RUN + 1);

  // Nothing new: exit quietly, without creating labels or writing logs.
  if (threads.length === 0) {
    return;
  }

  var hasBacklog = threads.length > MAX_THREADS_PER_RUN;
  if (hasBacklog) {
    threads = threads.slice(0, MAX_THREADS_PER_RUN);
  }

  // Create the "done" label only once we know there is actual work to do.
  var doneLabel = GmailApp.getUserLabelByName(DONE_LABEL_NAME);
  if (!doneLabel) {
    doneLabel = GmailApp.createLabel(DONE_LABEL_NAME);
    Logger.log('Created missing Gmail label: "%s"', DONE_LABEL_NAME);
  }

  var rootFolder = DriveApp.getFolderById(ROOT_FOLDER_ID);

  // Cache of month folders, so we do not query Drive for the same month twice.
  var monthFolderCache = {};

  var stats = {
    threadsFound:    threads.length,
    threadsLabeled:  0,
    threadsFailed:   0,
    filesSaved:      0,
    skippedInline:   0,
    skippedTooSmall: 0
  };

  Logger.log('Found %s thread(s) to process.', stats.threadsFound);

  for (var t = 0; t < threads.length; t++) {
    var thread = threads[t];
    var subject = '(no subject)';

    // Each thread is isolated: a single bad thread must not stop the run.
    try {
      subject = thread.getFirstMessageSubject() || '(no subject)';
      var messages = thread.getMessages();
      var savedInThread = 0;

      for (var m = 0; m < messages.length; m++) {
        var message = messages[m];

        // Target month folder is derived from the date of the message itself.
        var monthKey = Utilities.formatDate(message.getDate(), TIME_ZONE, 'yyyy-MM');

        // Two reads: one including inline images, one without. The difference
        // tells us how many embedded images (signatures, logos) were dropped.
        var allParts    = message.getAttachments({ includeInlineImages: true,  includeAttachments: true });
        var attachments = message.getAttachments({ includeInlineImages: false, includeAttachments: true });
        stats.skippedInline += (allParts.length - attachments.length);

        for (var a = 0; a < attachments.length; a++) {
          var attachment = attachments[a];
          var originalName = attachment.getName() || 'attachment';
          var size = attachment.getSize();

          if (size < MIN_FILE_SIZE_BYTES) { stats.skippedTooSmall++; Logger.log('Skipped (under 20KB, %s bytes): "%s" | thread: "%s"', size, originalName, subject); continue; } var monthFolder = getOrCreateMonthFolder_(rootFolder, monthKey, monthFolderCache); var finalName = buildUniqueFileName_(monthFolder, originalName); monthFolder.createFile(attachment.copyBlob().setName(finalName)); stats.filesSaved++; savedInThread++; Logger.log('Saved: "%s" -> %s/', finalName, monthKey);
        }
      }

      // Reached only if every attachment in this thread was handled without
      // throwing. A thread with no qualifying attachments is also considered
      // done, so it is not re-scanned on every future run.
      thread.addLabel(doneLabel);
      stats.threadsLabeled++;
      Logger.log('Thread done (%s file(s) saved), labeled "%s": "%s"',
                 savedInThread, DONE_LABEL_NAME, subject);

    } catch (err) {
      // Not labeled, so this thread will be retried on the next run.
      stats.threadsFailed++;
      Logger.log('FAILED thread "%s": %s', subject, (err && err.message) ? err.message : err);
    }
  }

  // ===== Run summary =====
  Logger.log('--- Summary ---');
  Logger.log('Threads processed this run: %s', stats.threadsFound);
  Logger.log('Threads labeled "%s": %s', DONE_LABEL_NAME, stats.threadsLabeled);
  Logger.log('Threads failed (left unlabeled for retry): %s', stats.threadsFailed);
  Logger.log('Files saved: %s', stats.filesSaved);
  Logger.log('Files skipped - inline/embedded images: %s', stats.skippedInline);
  Logger.log('Files skipped - smaller than 20KB: %s', stats.skippedTooSmall);

  if (hasBacklog) {
    Logger.log('NOTE: the %s-thread cap was reached. More unprocessed threads remain - run again.',
               MAX_THREADS_PER_RUN);
  }
}


/**
 * Returns the YYYY-MM sub-folder inside the root folder, creating it only if
 * it does not already exist. Results are cached per run.
 *
 * @param {Folder} rootFolder Parent folder.
 * @param {string} monthKey   Folder name, e.g. "2026-08".
 * @param {Object} cache      Run-scoped cache object.
 * @return {Folder}
 */
function getOrCreateMonthFolder_(rootFolder, monthKey, cache) {
  if (cache[monthKey]) {
    return cache[monthKey];
  }

  var folder;
  var existing = rootFolder.getFoldersByName(monthKey);
  if (existing.hasNext()) {
    folder = existing.next();
  } else {
    folder = rootFolder.createFolder(monthKey);
    Logger.log('Created month folder: %s', monthKey);
  }

  cache[monthKey] = folder;
  return folder;
}


/**
 * Returns a file name that does not collide with an existing file in the
 * folder. "invoice.pdf" becomes "invoice (1).pdf", "invoice (2).pdf", etc.
 *
 * @param {Folder} folder   Target folder.
 * @param {string} fileName Desired file name.
 * @return {string}
 */
function buildUniqueFileName_(folder, fileName) {
  if (!folder.getFilesByName(fileName).hasNext()) {
    return fileName;
  }

  var dotIndex = fileName.lastIndexOf('.');
  var base = (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName;
  var ext  = (dotIndex > 0) ? fileName.substring(dotIndex)    : '';

  var counter = 1;
  var candidate = base + ' (' + counter + ')' + ext;

  while (folder.getFilesByName(candidate).hasNext() && counter < 1000) {
    counter++;
    candidate = base + ' (' + counter + ')' + ext;
  }

  return candidate;
}
הישארו מעודכנים

רוצים לקבל עדכונים בלייב? רוצים מקום בו אתם יכולים להתייעץ עם מומחי AI, לשאול שאלות ולקבל תשובות? רוצים לשמוע על מבצעים והטבות לכלי ה-AI שמשנים את העולם? הצטרפו לקהילות ה-AI שלנו.

אפשר גם להרשם לניוזלטר שלנו

רוצים הרצאה או ייעוץ של ולדי פייסחין?
השאירו פרטים ונשמח לחזור אליכם עם המידע הרלוונטי
אולי יעניין אותך גם...

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

Let's update

רוצים לקבל עדכונים על כל מה שחדש ומעניין בעולם ה-AI? הרשמו לניוזלטר שלנו!

רוצים לראות יותר תכנים שלנו?

סמנו אותנו כמקור מועדף בגוגל וקבלו יותר כתבות, עדכונים ותובנות AI ישירות בתוצאות החיפוש!

הסבר קצר איך מוסיפים:

  • לוחצים על הכפתור "הוספה כמקור מועדף".
  • מסמנים את התיבה ליד Let’s AI.
  • סוגרים את החלון.
אירועי AI קרובים

תפריט נגישות

תוצאות נוספות...

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
וובינר חינמי שאסור לפספס
Power Users ו-Super Users
10.08.26 ב-20:00 בלייב זום