📋 جدول المحتويات
- ما الذي يفعله هذا السكريبت فعلياً، ولماذا يتفوق على تذكير الهاتف
- أشياء رائعة تستحق ضبط تذكير لها
- الدخول إلى script.google.com وإنشاء مشروعك
- ضبط تذكيراتك الخاصة
- تشغيله لأول مرة ومنح الإذن
- اختباره بأمان قبل أن تثق به
- ضبط المُشغّل اليومي حتى يعمل تلقائياً
- النسخة المحسّنة: تذكيرات مسبقة وحماية حصة الاستخدام
- حدود صادقة تستحق المعرفة
1. ما الذي يفعله هذا السكريبت فعلياً، ولماذا يتفوق على تذكير الهاتف
الفكرة الأساسية بسيطة: قائمة تُعرّفها مرة واحدة، تحتوي تواريخ ورسائل، يتم فحصها تلقائياً كل يوم بواسطة سكريبت يعمل على بنية جوجل التحتية الخاصة. عندما يطابق تاريخ اليوم شيئاً في قائمتك، يُرسل لك - أو لأي شخص آخر - التذكير عبر البريد الإلكتروني. لا تطبيق يُثبَّت، ولا هاتف يحتاج للبقاء مشحوناً ومُشغّلاً، ولا تذكير يموت بصمت لأنك بدّلت هاتفك قبل ثمانية عشر شهراً ونسيت إعادة تثبيت شيء ما. طالما حسابك على جوجل موجود، يستمر هذا بالعمل في الخلفية إلى أجل غير مسمى.
يتعامل السكريبت مع نوعين من التذكيرات بشكل مختلف: سنوي (Yearly)، للأشياء التي تتكرر كل سنة في نفس التاريخ - أعياد الميلاد، الذكريات السنوية - ومحدد (Exact)، للأحداث التي تحدث لمرة واحدة ومرتبطة بسنة محددة، مثل موعد نهائي لمشروع أو تجديد واحد لا يتكرر ضمن دورة سنوية ثابتة.
2. أشياء رائعة تستحق ضبط تذكير لها
🪪 تجديد رخصة القيادة
الشيء الأكثر شيوعاً الذي ينساه الناس حتى تنتهي صلاحيته فعلياً.
🚗 تسجيل السيارة والتأمين
كلاهما يميل للتجديد سنوياً في تاريخ ثابت - مثالي للنوع السنوي (Yearly).
🛂 انتهاء صلاحية جواز السفر
جوازات السفر تعمل بدورات متعددة السنوات، مما يجعلها مناسبة تماماً للنوع المحدد (Exact).
🌐 تجديد اسم النطاق واستضافة الويب
خسارة نطاق بسبب انتهاء صلاحية بطاقة التجديد التلقائي كارثة شائعة فعلياً ويمكن تفاديها بالكامل.
🎂 أعياد الميلاد والذكريات السنوية
اضبطه مرة واحدة، ويُرسل لك بريداً كل سنة دون الحاجة لإعادة إدخاله.
🐾 مواعيد تطعيم الحيوانات الأليفة
تذكيرات الطبيب البيطري غالباً تصل قريبة جداً من الموعد لتتمكن فعلياً من حجز موعد في الوقت المناسب.
🦷 فحوصات الأسنان والعيون
من السهل ترك الفحوصات السنوية تفوت سنة كاملة دون أي تذكير على الإطلاق.
🧾 مواعيد تقديم الضرائب
تاريخ سنوي ثابت يستفيد بشكل هائل من تذكير قبل الموعد بوقت كافٍ، لا في اليوم نفسه.
🔧 صيانة المنزل
تغيير فلاتر التكييف، وتصريف سخان الماء، واستبدال بطاريات كاشف الدخان - كلها سهلة النسيان، وكلها تأمين رخيص ضد مشاكل أكبر.
3. الدخول إلى script.google.com وإنشاء مشروعك
- افتح متصفحاً واذهب إلى script.google.com، مسجّلاً الدخول بحساب جوجل الذي تريده أن يرسل هذه التذكيرات.
- انقر New project في المنطقة العلوية اليسرى من الصفحة.
- ستجد نفسك في محرر الكود مع دالة فارغة افتراضية موجودة بالفعل. حدّد كل ذلك الكود النائب واحذفه.
- الصق السكريبت الكامل الموجود لاحقاً في هذه المقالة.
- انقر على اسم المشروع بلا عنوان أعلى الصفحة وأعطه اسماً واضحاً، مثل "Email Reminders".
- اضغط Ctrl+S (أو Cmd+S على ماك) للحفظ.
4. ضبط تذكيراتك الخاصة
كل شيء ستحتاج لتغييره يومياً موجود داخل قائمة EMAIL_TASKS بالقرب من أعلى السكريبت. كل إدخال يحتاج تعبئة هذه الحقول:
- id - تسمية فريدة لذلك التذكير المحدد. مطلوبة أن تكون فريدة بشكل صارم فقط لتذكيرات النوع "Exact"، بما أنها ما يستخدمه السكريبت لتذكّر أن بريداً قد أُرسل بالفعل.
- recipient - عنوان البريد الإلكتروني الذي يجب أن يستلمه. لا يشترط أن يكون عنوانك الخاص.
- subject - سطر عنوان الرسالة.
- message - نص محتوى التذكير.
- type - إما
"Yearly"لأي شيء يتكرر سنوياً، أو"Exact"لتاريخ محدد لمرة واحدة. - targetDate - بصيغة
"d-mon"للنوع السنوي (مثل"5-sep")، أو"d-mon-yyyy"للنوع المحدد (مثل"1-sep-2027").
لإضافة تذكير جديد، انسخ إدخالاً موجوداً داخل الأقواس المعقوفة، عدّل كل حقل، وتأكد من وجود فاصلة تفصله عن الإدخال الذي قبله.
5. تشغيله لأول مرة ومنح الإذن
في أول مرة تُشغّل فيها أي دالة في هذا السكريبت، تحتاج جوجل لإذنك الصريح لإرسال بريد نيابة عنك - هذا جزء قياسي من طريقة عمل Apps Script لكل مشروع، وليس شيئاً خاصاً بهذا السكريبت تحديداً.
- أعلى المحرر، استخدم قائمة الدالة المنسدلة لاختيار checkAndSendEmails.
- انقر زر Run.
- ستظهر نافذة بعنوان Authorization required. انقر Review permissions.
- اختر حساب جوجل الذي تريده تشغيل هذا السكريبت.
- على الأغلب سترى شاشة تحذّر "Google hasn't verified this app" - هذا طبيعي تماماً ومتوقع لسكريبت شخصي كتبته بنفسك؛ إنه ليس تحذيراً بخصوص كود خبيث. انقر Advanced، ثم انقر Go to [اسم مشروعك] (unsafe).
- راجع الأذونات المطلوبة - إرسال بريد نيابة عنك، وعرض بيانات سكريبتك المخزَّنة - ثم انقر Allow.
6. اختباره بأمان قبل أن تثق به
قبل ضبطه ليعمل تلقائياً، تأكد أنه يعمل فعلاً بالطريقة التي تتوقعها.
- غيّر مؤقتاً
targetDateلمهمة واحدة إلى تاريخ اليوم، بالصيغة الصحيحة لنوعها. - شغّل checkAndSendEmails مجدداً من القائمة المنسدلة للدالة.
- تحقق من سجل التنفيذ (Execution log) أسفل الشاشة - التشغيل الناجح يُظهر سطراً يؤكد إرسال البريد.
- تحقق من صندوق الوارد الفعلي لعنوان المستلم للتأكد أنه وصل فعلياً.
- إذا كنت تختبر تذكير "Exact" وتريد إطلاقه مجدداً بعد اختبار ناجح، شغّل دالة resetAllSentStatuses أولاً - وإلا سيرفض السكريبت بشكل صحيح إرساله مرة ثانية، تماماً كما صُمم.
- بمجرد التأكد، أعد تاريخ الاختبار إلى قيمته الفعلية والمقصودة.
7. ضبط المُشغّل اليومي حتى يعمل تلقائياً
- اختر setupDailyAutoSend من القائمة المنسدلة للدالة أعلى الصفحة.
- انقر Run مرة واحدة. هذا ينشئ الجدولة اليومية المؤتمتة - لن تحتاج أبداً لتشغيل هذه الدالة تحديداً مجدداً ما لم ترغب بإعادة ضبطها.
- انقر أيقونة الساعة في الشريط الجانبي الأيسر، بعنوان Triggers، للتأكد أنها أُنشئت. يجب أن ترى checkAndSendEmails مُدرجة، مضبوطة على العمل يومياً.
من هذه اللحظة فصاعداً، تفحص خوادم جوجل قائمتك كل يوم وفق جدولتها الخاصة - لا يحتاج السكريبت متصفحك مفتوحاً، ولا حاسوبك مُشغّلاً، ولا أي شيء آخر من جهتك.
8. النسخة المحسّنة: تذكيرات مسبقة وحماية حصة الاستخدام
السكريبت الأصلي يرسل تذكيراً بالضبط في التاريخ المستهدف. بالنسبة للكثير من التذكيرات الحقيقية - تجديد رخصة، انتهاء صلاحية جواز سفر - معرفة الأمر قبل ثلاثين يوماً أكثر فائدة بكثير من اكتشافه في اليوم نفسه، حين قد يكون الأوان قد فات فعلياً للتصرف. النسخة المحسّنة أدناه تضيف قائمة اختيارية daysBefore لأي مهمة، تتيح لك إطلاق عدة تحذيرات مسبقة لنفس الحدث، بالإضافة إلى فحص مدمج مقابل حصة الإرسال اليومية لـGmail بحيث يسجّل السكريبت تحذيراً واضحاً بدل الفشل بصمت إذا اقتربت يوماً من ذلك الحد.
/**
* 1. CONFIGURE YOUR EMAILS HERE
* -------------------------------------------------
* type: "Yearly" (recurring) OR "Exact" (one-time)
* targetDate format for Yearly: "d-mon" (e.g., "5-sep")
* targetDate format for Exact: "d-mon-yyyy" (e.g., "1-sep-2027")
* id: MUST be unique for every "Exact" email so the script remembers it was sent.
* daysBefore: OPTIONAL array of advance-notice days, e.g. [30, 7, 1]
* Leave it out entirely to only send on the exact date, like before.
*/
const EMAIL_TASKS = [
{
id: "task_001",
recipient: "عنوان البريد الإلكتروني هذا محمي من روبوتات السبام. يجب عليك تفعيل الجافاسكربت لرؤيته. ",
subject: "Happy Birthday!",
message: "Wishing you a wonderful birthday today!",
type: "Yearly",
targetDate: "5-sep"
},
{
id: "task_002",
recipient: "عنوان البريد الإلكتروني هذا محمي من روبوتات السبام. يجب عليك تفعيل الجافاسكربت لرؤيته. ",
subject: "Project Launch",
message: "This is the automated reminder for the project launch.",
type: "Exact",
targetDate: "1-sep-2027"
},
{
id: "task_003",
recipient: "عنوان البريد الإلكتروني هذا محمي من روبوتات السبام. يجب عليك تفعيل الجافاسكربت لرؤيته. ",
subject: "Driver's License Renewal Coming Up",
message: "Your driver's license renews soon. This is your advance heads-up.",
type: "Yearly",
targetDate: "15-jun",
daysBefore: [30, 7, 1]
}
];
/**
* 2. MAIN FUNCTION (Runs daily via trigger)
* -------------------------------------------------
*/
function checkAndSendEmails() {
const today = new Date();
const timezone = Session.getScriptTimeZone();
const properties = PropertiesService.getScriptProperties();
const todayYearly = Utilities.formatDate(today, timezone, "d-MMM").toLowerCase();
const todayExact = Utilities.formatDate(today, timezone, "d-MMM-yyyy").toLowerCase();
const remainingQuota = MailApp.getRemainingDailyQuota();
if (remainingQuota <= 0) {
console.error("Daily email quota reached. No emails will be sent today.");
return;
}
EMAIL_TASKS.forEach(task => {
const offsets = (task.daysBefore && task.daysBefore.length) ? task.daysBefore : [0];
offsets.forEach(offsetDays => {
let shouldSend = false;
let storageKey = task.id + "_" + offsetDays;
if (task.type === "Yearly") {
const parts = task.targetDate.toLowerCase().trim().split("-");
const day = parseInt(parts[0], 10);
const monthStr = parts[1];
const currentYear = today.getFullYear();
// Check both this year and next year to correctly handle
// reminders that cross the December/January boundary.
[currentYear, currentYear + 1].forEach(candidateYear => {
const monthIndex = new Date(Date.parse(monthStr + " 1, " + candidateYear)).getMonth();
const targetDateObj = new Date(candidateYear, monthIndex, day);
targetDateObj.setDate(targetDateObj.getDate() - offsetDays);
const formatted = Utilities.formatDate(targetDateObj, timezone, "d-MMM").toLowerCase();
if (formatted === todayYearly) {
shouldSend = true;
}
});
}
else if (task.type === "Exact") {
const parts = task.targetDate.toLowerCase().trim().split("-");
const day = parseInt(parts[0], 10);
const monthStr = parts[1];
const year = parseInt(parts[2], 10);
const monthIndex = new Date(Date.parse(monthStr + " 1, " + year)).getMonth();
const targetDateObj = new Date(year, monthIndex, day);
targetDateObj.setDate(targetDateObj.getDate() - offsetDays);
const formatted = Utilities.formatDate(targetDateObj, timezone, "d-MMM-yyyy").toLowerCase();
if (formatted === todayExact) {
const hasBeenSent = properties.getProperty(storageKey);
if (!hasBeenSent) {
shouldSend = true;
}
}
}
if (shouldSend) {
try {
const prefix = offsetDays > 0 ? "[" + offsetDays + " day" + (offsetDays > 1 ? "s" : "") + " reminder] " : "";
MailApp.sendEmail(task.recipient, prefix + task.subject, task.message);
console.log("Email sent to " + task.recipient + " (offset: " + offsetDays + " days)");
if (task.type === "Exact") {
properties.setProperty(storageKey, "SENT");
}
} catch (error) {
console.error("Failed to send email to " + task.recipient + ": " + error.message);
}
}
});
});
}
/**
* 3. AUTOMATION SETUP
* -------------------------------------------------
* Run this function ONCE manually to set up the daily automation.
*/
function setupDailyAutoSend() {
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach(trigger => {
if (trigger.getHandlerFunction() === "checkAndSendEmails") {
ScriptApp.deleteTrigger(trigger);
}
});
ScriptApp.newTrigger("checkAndSendEmails")
.timeBased()
.atHour(8)
.everyDays(1)
.create();
console.log("Automation Active! The script will now check and send emails daily at 8 AM.");
}
/**
* UTILITY: Reset Sent Status (For testing purposes)
* -------------------------------------------------
* Run this if you want to test an "Exact" email again after it has already sent.
*/
function resetAllSentStatuses() {
PropertiesService.getScriptProperties().deleteAllProperties();
console.log("Memory cleared. All 'Exact' emails are ready to be tested/sent again.");
}
كل شيء يخص الإعداد، والاختبار، والمُشغّلات الموصوفة أعلاه يعمل بشكل مطابق مع هذه النسخة المحسّنة. الشيء الجديد الوحيد الذي يجب فهمه هو الحقل الاختياري daysBefore - اتركه خارج أي مهمة تماماً، وستتصرف تلك المهمة تماماً كالسكريبت الأصلي، فتُطلق فقط في التاريخ المحدد بالضبط.
9. حدود صادقة تستحق المعرفة
- حسابات Gmail المجانية لديها حصة إرسال يومية عبر Apps Script - حسابات Google Workspace تحصل على حد أعلى بكثير. فحص الحصة المدمج في السكريبت المحسّن يسجّل تحذيراً واضحاً بدل الفشل بصمت إذا اقتربت منه يوماً.
- المُشغّلات القائمة على الوقت تعمل ضمن نافذة مدتها ساعة بدل الدقيقة المحددة بالضبط - إعداد
atHour(8)يعني في وقت ما بين الساعة 8:00 و9:00 صباحاً، لا بالضبط عند الساعة. - إذا ألغيت أذونات هذا السكريبت يوماً من إعدادات أمان حساب جوجل الخاص بك، يتوقف المُشغّل عن العمل بصمت حتى تُعيد التفويض له.
- هذا يُرسل من عنوان Gmail الخاص بك، لذا سيرى المستلمون بريدك كالمُرسِل - إنه أنت فعلياً من يرسله، وليس خدمة إشعارات مجهولة.
استكشف المزيد من محتوى التوعية والأمان
اكتشف المزيد من النصائح الأمنية، وتحليل التهديدات، وتوعية القرصنة، والأدلة العملية المصممة لمساعدتك على البقاء آمناً عبر الإنترنت.
زيارة التوعية والأمان ←كتابة خليل شريتح - باحث في الأمن السيبراني وخبير وسائل التواصل الاجتماعي. الموقع الرسمي: shreateh.net