بناء أداة إرسال رسائل إخبارية برمجية باستخدام SendGrid API و Node.js

دقائق القراءة: 22

في عالم التسويق الرقمي وتطوير الويب، يُعد إرسال الرسائل الإخبارية (Newsletters) جزءًا حيويًا للتواصل مع الجمهور. لطالما اعتمدت منصات مثل freeCodeCamp على حلول قوية لإدارة هذه العملية. في السابق، كان كوينسي لارسون يرسل رسالته الإخبارية الأسبوعية عبر منصة Mail for Good المدعومة من Amazon SES، لكنه قام مؤخرًا بترحيل هذه العملية إلى SendGrid. في هذا المقال، سنستعرض خطوة بخطوة كيفية بناء أداة برمجية متكاملة لتحقيق ذلك، مع التركيز على الممارسات الأمثل لضمان وصول رسائلك.

تهيئة حساب SendGrid: دليلك الشامل

الخطوة الأولى والأساسية هي التسجيل في SendGrid وإنشاء حسابك. لأغراض هذا الشرح العملي، ستكون الطبقة المجانية (free tier) كافية تمامًا. ومع نمو تطبيقك وزيادة حجم رسائلك، قد تحتاج إلى ترقية حسابك لزيادة حدود الإرسال المتاحة عبر المنصة.

إعداد عنوان IP مخصص في SendGrid

بشكل افتراضي، يستخدم SendGrid عناوين IP مشتركة لإرسال رسائل البريد الإلكتروني. قد يكون هذا مقبولاً لتطبيقات البريد الإلكتروني ذات النطاق الأصغر، ولكن مع زيادة معدلات الإرسال لديك، ستحتاج إلى إعداد عنوان IP مخصص. يُعد هذا خيارًا ممتازًا لأن “سمعة المرسل” (sender reputation) – وهو المقياس الذي يستخدمه SendGrid لتقييم وضعك لدى موفري خدمات البريد الإلكتروني – لن تتأثر سلبًا بإجراءات المستخدمين الآخرين الذين يشاركون نفس عنوان IP. لتهيئة عنوان IP مخصص خاص بك، اختر خيار "Settings" من قائمة التنقل الجانبية، ثم اختر "IP Addresses". تجدر الإشارة إلى أن هذا الخيار غير متاح في الطبقة المجانية. اعتمادًا على خطتك المدفوعة، قد يكون لديك بالفعل عنوان IP مخصص واحد مُعد. إذا لم يكن لديك واحد، أو إذا اخترت إضافة المزيد، يمكنك تحديد زر "Add an IP address" لتهيئة عنوان IP جديد.

قائمة إعدادات SendGrid تظهر خيار 'IP Addresses' لتهيئة عناوين IP المخصصة.

قائمة الإعدادات لعناوين IP

تفويض مرسل البريد الإلكتروني في SendGrid

ملاحظة: يمكنك تخطي هذا القسم إذا كنت تستخدم نطاقًا مخصصًا (custom domain) لرسائل البريد الإلكتروني الخاصة بك.

لإرسال رسائل بريد إلكتروني من عنوان بريدك الإلكتروني الشخصي، ستحتاج إلى التحقق من أن عنوان البريد الإلكتروني هذا يخصك بالفعل. في القائمة اليسرى، اختر "Settings"، ثم "Sender Authentication". اختر "Verify a Single Sender" لتتبع خطوات إضافة عنوان بريد إلكتروني واحد خاص بك.

شاشة SendGrid تعرض خيار 'Verify a Single Sender' لتوثيق عنوان بريد إلكتروني واحد.

خيار المرسل الفردي

توثيق نطاقك المخصص في SendGrid

ملاحظة: يمكنك تخطي هذا القسم إذا كنت لا تستخدم نطاقًا مخصصًا لرسائل البريد الإلكتروني الخاصة بك.

لإرسال رسائل بريد إلكتروني من نطاق بريدك المخصص، ستحتاج إلى توثيق هذا النطاق مع SendGrid. للوصول إلى هذه الشاشة، اختر قائمة "Settings" مرة أخرى، ثم اختر "Sender Authentication".

قائمة إعدادات SendGrid تظهر خيار 'Sender Authentication' لتوثيق النطاق.

قائمة الإعدادات لتوثيق المرسل

بعد ذلك، ستظهر لك شاشة بها خيار "Domain Authentication". اختر خيار "Authenticate Your Domain" وسيقوم SendGrid بإرشادك خلال عملية تهيئة سجلات DNS الخاصة بك (مع تعليمات محددة بناءً على مزود DNS الخاص بك).

صفحة إعدادات توثيق المرسل في SendGrid تعرض خيار 'Authenticate Your Domain' لتوثيق النطاق.

صفحة إعدادات توثيق المرسل

إعداد Reverse DNS في SendGrid

ملاحظة: يمكنك تخطي هذا القسم إذا كنت لا تستخدم نطاقًا مخصصًا لرسائل البريد الإلكتروني الخاصة بك.

يستخدم موفرو البريد الإلكتروني نظام أسماء النطاقات العكسي (Reverse DNS) للبحث عن مالك عنوان IP معين. سيسمح لك إعداد هذا بالتحقق من أن عنوان IP الذي ترسل منه البريد الإلكتروني متصل بنطاقك المخصص، مما يعزز مصداقية رسائلك. في نفس شاشة Sender Authentication المذكورة أعلاه، سترى قسم "Reverse DNS". سيكون هناك خيار لتهيئة Reverse DNS لكل عنوان IP مخصص لديك في حسابك – مثل توثيق النطاق، ستُرشدك منصة SendGrid خلال عملية إعداد ذلك.

تهيئة توثيق البريد الإلكتروني في SendGrid (SPF, DKIM, DMARC)

ملاحظة: يمكنك تخطي هذا القسم إذا كنت لا تستخدم نطاقًا مخصصًا لرسائل البريد الإلكتروني الخاصة بك.

يستخدم موفرو البريد الإلكتروني الرئيسيون (مثل Gmail و Yahoo و Outlook) عدة طرق لتوثيق مرسل البريد الإلكتروني لضمان الأمان ومنع الاحتيال. هذه الطرق هي SPF و DKIM و DMARC.

  • SPF (Sender Policy Framework): يتحقق من أن عنوان IP الذي يرسل البريد من نطاقك مصرح له بذلك. هذا يساعد على منع المحتالين من إرسال رسائل بريد إلكتروني تبدو وكأنها قادمة من نطاقك.
  • DKIM (DomainKeys Identified Mail): يستخدم سلاسل مفاتيح عامة لتوثيق أن عنوان البريد الإلكتروني from دقيق وغير مزور أو محرف. يضيف توقيعًا رقميًا إلى رسائل البريد الإلكتروني الصادرة.
  • DMARC (Domain-based Message Authentication, Reporting, and Conformance): هو مجموعة من التعليمات التي تخبر موفري البريد الإلكتروني بكيفية التصرف عندما تفشل رسالة بريد إلكتروني في عمليات التحقق من SPF أو DKIM.

ستُرشدك عملية توثيق SendGrid خلال إعداد SPF و DKIM كجزء من عملية توثيق النطاق. ومع ذلك، ستحتاج إلى تهيئة DMARC يدويًا. قم بزيارة مزود استضافة DNS الخاص بك وادخل إلى إعدادات إدارة DNS. من هناك، أضف سجل TXT جديدًا باسم _dmarc.yourdomain.com (مع استبدال yourdomain.com بنطاقك المخصص). لاحظ أن بعض المزودين، مثل GoDaddy، سيضيفون نطاقك تلقائيًا إلى السجل – في هذه الحالة، يجب أن يكون الاسم _dmarc. يجب أن يأخذ هذا السجل بنية مماثلة للتالي:

"v=DMARC1; p=none; pct=100; rua=mailto:dmarc@yourdomain.com"
  • v=DMARC1: يشير إلى إصدار قواعد DMARC المراد استخدامها (الإصدار 1 هو الوحيد المتاح حاليًا).
  • p=none: يشير إلى الإجراء الذي يجب أن يتخذه مزود البريد الإلكتروني عندما تفشل رسالة بريد إلكتروني في DKIM أو SPF. يجب أن تبدأ هذه القيمة بـ none لتجنب التأثير على إمكانية تسليم رسائل البريد الإلكتروني الخاصة بك. بمجرد تأكيد تهيئة DKIM و SPF بشكل صحيح، يمكنك تحديث هذه القيمة إلى quarantine لجعل المزودين يوجهون رسائل البريد الإلكتروني الفاشلة تلقائيًا إلى مجلد البريد العشوائي (spam)، أو reject لجعل المزودين يرفضون/يرتدون رسائل البريد الإلكتروني الفاشلة.
  • pct=100: يشير إلى النسبة المئوية لرسائل البريد الإلكتروني الفاشلة التي يجب تطبيق الإجراء عليها.
  • rua=mailto:dmarc@yourdomain.com: هو عنوان البريد الإلكتروني لإرسال التقارير المجمعة إليه. تحتوي هذه التقارير على معلومات حول جميع رسائل البريد الإلكتروني من عناوين IP الخاصة بك التي تلقاها مزود معين. استبدل dmarc@yourdomain.com بعنوان البريد الإلكتروني الذي تريد تلقي هذه التقارير عليه.

إنشاء قالب ديناميكي في SendGrid

تعتمد الأداة التي سنبنيها اليوم على ميزة القوالب الديناميكية (dynamic template) في SendGrid لتعيين موضوع ونص رسالة البريد الإلكتروني. لإعداد ذلك، اختر خيار "Email API" في قائمة التنقل الجانبية، ثم اختر "Dynamic Templates".

قائمة إعدادات SendGrid تعرض خيار 'Dynamic Templates' ضمن 'Email API'.

قائمة الإعدادات للقوالب الديناميكية

ستظهر لك شاشة بها مطالبة بإنشاء أول قالب ديناميكي لك. اختر خيار "Create a Dynamic Template". امنح قالبك الجديد اسمًا، على سبيل المثال: "freeCodeCamp SendGrid Tutorial". سيضيف SendGrid هذا القالب إلى قائمة القوالب المتاحة. اختر القالب لرؤية Template ID (دوّن هذا، حيث سنحتاجه للأداة لاحقًا) وانقر على زر "Add Version".

شاشة معاينة SendGrid تعرض القالب الديناميكي الجديد 'freeCodeCamp SendGrid Tutorial' مع خيار إضافة إصدار.

معاينة القالب المضاف حديثًا

اختر "Blank Template" على الشاشة التي تظهر، ثم اختر "Code Editor". يجب أن ترى الآن عرض المحرر. يستخدم محرر SendGrid لغة HTML لبناء نص البريد الإلكتروني – ومع ذلك، عندما نبني أداتنا، سنرسل النسخة النصية العادية (plain text). في الوقت الحالي، استبدل محتويات المحرر بالرمز التالي:

 <p>This is a test email used with the freeCodeCamp SendGrid tutorial</p> 
 <p>Unsubscribe: {{{unsubscribeId}}}</p>

ستلاحظ أننا أضفنا {{{unsubscribeId}}}. يستخدم قالب SendGrid مكتبة Handlebars لاستبدال القيم ديناميكيًا – سنستفيد من هذه الميزة عندما نبني الأداة. الآن اختر خيار الإعدادات من أعلى اليسار – يمكنك اختياريًا إعطاء إصدار القالب الخاص بك اسمًا، ولكن حقل "Subject" هو ما نريد تعديله. اضبط هذه القيمة على {{{subject}}} لتحميل قيمة الموضوع ديناميكيًا من أداتنا.

لاختبار القالب الديناميكي، اختر خيار "Test Data" من القائمة العلوية. أدخل بيانات JSON هذه في المحرر هناك:

{ 
   "unsubscribeId": "1", 
   "subject": "Testing emails!" 
}

يجب أن ترى الآن المعاينة على الجانب الأيمن من الشاشة تعكس هذه القيم في القالب. تذكر النقر على زر Save لحفظ تغييراتك!

شاشة محرر SendGrid ومعاينة تظهر التحميل الديناميكي لقيم القالب.

شاشة المحرر والمعاينة تظهر التحميل الديناميكي لقيم القالب

إنشاء مفتاح API في SendGrid

الخطوة الأخيرة في تهيئة حساب SendGrid الخاص بك هي إنشاء مفتاح API لاستخدامه في أداتنا. انقر على السهم الخلفي في أعلى اليسار للعودة إلى صفحة SendGrid الرئيسية. ثم اختر "Settings" و "API keys". اختر "Create API Key" لإنشاء مفتاح جديد. يمكنك اختياريًا منح "Full Access" لمفتاحك، ولكن لأغراض هذا الشرح العملي، ستحتاج فقط إلى وصول "Mail Send". تأكد من إعطاء مفتاحك اسمًا وصفيًا حتى تتذكر الغرض منه إذا قمت بالوصول إلى هذه الشاشة مرة أخرى. بمجرد تهيئة أذوناتك، اختر "Create and View" لإنشاء المفتاح – احفظ هذا المفتاح في مكان آمن حيث لن تتمكن من عرضه مرة أخرى.

شاشة إنشاء مفتاح API في SendGrid مع تمكين إذن 'Mail Send'.

شاشة إنشاء مفتاح API مع تمكين إذن إرسال البريد

بناء أداة إرسال البريد الإلكتروني

حان الوقت الآن لكتابة الكود لإرسال بعض رسائل البريد الإلكتروني فعليًا. يمكننا عرض الكود لتطبيقنا الحي، ولكن لأغراض هذا الشرح العملي، سنقوم ببناء نسخة مبسطة قليلاً للتركيز بشكل أساسي على استخدام SendGrid API.

البرمجيات المطلوبة لسكربت حملة بريد إلكتروني مخصص

ستحتاج إلى تثبيت الأدوات التالية للعمل مع هذا المشروع:

  • Node.js – يوصى بإصدار LTS.
  • بيئة تطوير متكاملة (IDE)، مثل VSCode أو Atom.

قد ترغب أيضًا اختياريًا في استخدام git للتحكم في الإصدارات.

تستخدم أداتنا الحية مجموعة MongoDB Atlas، لكن مثالنا في الشرح العملي لن يستخدمها. إذا لم تكن على دراية بـ MongoDB، فإن منهج freeCodeCamp يتضمن قسمًا ممتازًا حول إعداد واستخدام MongoDB.

تهيئة المشروع

قم بإنشاء دليل (مجلد) للعمل على هذا المشروع. ثم افتح هذا المجلد باستخدام المحرر والطرفية التي تختارها. للبدء، سنحتاج إلى إعداد هذا كمشروع Node. أسرع طريقة للقيام بذلك هي باستخدام npm init في طرفيتك. سيُرشدك هذا خلال إنشاء ملف package.json وهو الملف الأساسي لتطبيق Node. ستعمل القيم الافتراضية بشكل جيد لتطبيقنا، ولكننا سنرغب في تعديل قسم scripts:

 "scripts": {
   "build": "tsc",
   "send": "node ./prod/send.js"
 },

سيتم استخدام سكربت build لتجميع TypeScript الخاص بنا إلى JavaScript، وسيقوم سكربت send بتشغيل تطبيقنا.

بعد ذلك، سنقوم بتثبيت وإعداد TypeScript. إذا لم تكن على دراية بـ TypeScript، فهو في الأساس مجموعة فائقة من JavaScript مع تعريفات أنواع أقوى وفحص أخطاء وقت التجميع (compile-time). لتثبيت TypeScript على مشروعك، قم بتشغيل npm install --save-dev typescript في طرفيتك. (العلامة --save-dev تحفظه كاعتماد تطوير – لا يلزم TypeScript في وقت التشغيل لذا يمكن تنظيفه في بيئة الإنتاج).

يتطلب TypeScript ملف تهيئة خاص به لتعيين القواعد التي يجب أن يتبعها عند إنشاء ملفات JavaScript. أنشئ ملفًا في الدليل الجذر لمشروعك يسمى tsconfig.json وأدخل ما يلي:

{ 
   "compilerOptions": { 
     "target": "es5", 
     "module": "commonjs", 
     "strict": true, 
     "esModuleInterop": true, 
     "skipLibCheck": true, 
     "forceConsistentCasingInFileNames": true, 
     "outDir": "./prod", 
     "rootDir": "./src" 
   } 
}

من أجل الإيجاز، لن نتعمق في إعدادات التهيئة هذه. إذا كنت ترغب في معلومات إضافية، فإن TypeScript لديه وثائق متعمقة للغاية.

إذا كنت تستخدم git للتحكم في الإصدارات وتحميل هذا إلى مستودع (مثل GitHub)، فستحتاج إلى إنشاء ملف .gitignore في الدليل الجذر لمشروعك. يجب أن يحتوي هذا الملف على:

/node_modules/
.env
/prod/
  • /node_modules/: سيتجاهل الحزم المثبتة. يعتبر هذا أفضل ممارسة عند العمل مع التحكم في الإصدارات.
  • .env: سيتجاهل ملف متغيرات البيئة الخاص بنا. هذا مهم جدًا حيث أنك لا ترغب أبدًا في نشر أسرارك إلى مستودع عام.
  • /prod/: سيتجاهل ملفات JavaScript المجمعة. سنستخدم هذا المجلد أيضًا لقوائم البريد الإلكتروني الخاصة بنا، لذا من المهم تجنب نشر تلك المعلومات الشخصية القابلة للتحديد عن طريق الخطأ.

أنشئ ملف .env في الدليل الجذر لمشروعك. سنقوم بتحميل متغيرات البيئة التالية من خلال هذا الملف:

SENDGRID_API_KEY=
SENDGRID_FROM=
SENDGRID_TEMPLATE_ID=
MAIL_SUBJECT=
  • SENDGRID_API_KEY: يجب أن يكون مفتاح API الذي أنشأته في الخطوات السابقة.
  • SENDGRID_FROM: يجب أن يكون عنوان بريدك الإلكتروني (هذا هو العنوان المستخدم لحقل from).
  • SENDGRID_TEMPLATE_ID: يجب أن يكون سلسلة id للقالب الديناميكي الذي أنشأته سابقًا.
  • MAIL_SUBJECT: سيكون سطر الموضوع لرسائل البريد الإلكتروني التي ترسلها. في الوقت الحالي، اضبط هذا على "fCC Tutorial Email".

أخيرًا، أنشئ مجلد src في الدليل الجذر لمشروعك، وأنشئ ملف send.ts في هذا المجلد.

تثبيت الاعتمادات

أولاً، نحتاج إلى تثبيت حزمة sendgrid الخاصة بـ Node.js. تعمل هذه الحزمة كغلاف لـ SendGrid API وستعمل على تبسيط عملية إجراء مكالمات API لإرسال رسائل البريد الإلكتروني. قم بتشغيل npm install @sendgrid/mail لتثبيت هذه الحزمة.

بعد ذلك، نحتاج إلى بعض اعتمادات التطوير (development dependencies). قم بتشغيل npm install --save-dev dotenv @types/node.

  • dotenv: سيسمح لنا بتحميل متغيرات البيئة من ملف .env محليًا.
  • @types/node: يوفر تعريفات النوع لـ Node.js – يعتمد TypeScript على هذه التعريفات لفهم بنية الأساليب والوظائف المضمنة.

كتابة منطق التطبيق

الآن سنعمل في ملف /src/send.ts الخاص بنا – هذا هو المكان الذي نبني فيه الجزء الأكبر من منطق تطبيقنا. سنبدأ باستيراد القيم المطلوبة من حزمنا. أولاً، نريد تحميل حزمة dotenv وتحليل متغيرات البيئة الخاصة بنا.

 import dotenv from "dotenv";
 dotenv.config();

تقوم مكالمة dotenv.config() بقراءة ملف .env الخاص بنا وتحميل القيم في كائن process.env الخاص بـ Node.

بعد ذلك، نستورد الوحدات المطلوبة من حزمة SendGrid:

 import sgMail, { MailDataRequired } from "@sendgrid/mail";

يمثل sgMail غلاف API الأساسي، و MailDataRequired هو تعريف نوع سنحتاجه.

أخيرًا، نستورد بعض ميزات Node المضمنة للتعامل مع ملفاتنا:

 import path from "path";
 import { createWriteStream, readFile } from "fs";
  • path: سيتم استخدامه لتحديد موقع ملفات قائمة البريد الإلكتروني الخاصة بنا بمسارات نسبية.
  • fs: سيتم استخدامه للقراءة والكتابة في تلك الملفات.

حان الوقت لبدء بناء المنطق! يعتمد تطبيقنا على بعض القيم الأساسية التي يجب تعيينها في ملف .env، لذلك نحتاج إلى البدء بالتحقق من تعيين هذه المتغيرات بشكل صحيح. إذا كان أي منها مفقودًا، فنحن نريد أن يخرج تطبيقنا مبكرًا لتجنب إلقاء الأخطاء عندما نرسل رسائل البريد الإلكتروني.

 // هنا نتحقق من مفتاح API صالح
 const apiKey = process.env.SENDGRID_API_KEY;
 if (!apiKey) {
   console.error("Missing SendGrid Key");
   process.exit(1);
 }

 // هنا نتحقق من عنوان مرسل صالح
 const fromAddress = process.env.SENDGRID_FROM;
 if (!fromAddress) {
   console.error("Missing sender email address!");
   process.exit(1);
 }

 // هنا نتحقق من معرف القالب الديناميكي
 const sgTemplate = process.env.SENDGRID_TEMPLATE_ID;
 if (!sgTemplate) {
   console.error("Missing SendGrid Template ID");
   process.exit(1);
 }

 // هنا نتحقق من موضوع البريد، ولكن إذا كان مفقودًا
 // لا نحتاج إلى الخروج. بدلاً من ذلك نستخدم قيمة احتياطية.
 const subjectValue = process.env.MAIL_SUBJECT || "Fallback value - check your env!";

تخبر مكالمة process.exit(1) التي تراها في كل فحص شرطي Node بإنهاء العملية (تطبيقنا) برمز خروج 1. يشير هذا إلى أن تطبيقنا تعطل بسبب فشل أحد هذه الفحوصات.

يتطلب SendGrid منا تعيين مفتاح API. أسفل منطق متغيرات البيئة الخاص بك، أضف استدعاء الدالة لتعيين المفتاح.

 // هنا نعيّن مفتاح SendGrid API
 sgMail.setApiKey(apiKey);

قبل المضي قدمًا، قم بتشغيل npm run build في طرفيتك – سيؤدي هذا إلى إنشاء مجلد prod يحتوي على JavaScript المترجم الخاص بنا. يجب أن ترى الآن بنية الملفات التالية:

شجرة الملفات للمشروع في هذه المرحلة من الشرح العملي.

شجرة الملفات لهذه النقطة في الشرح العملي

في هذه المرحلة، إذا كنت تستخدم git، فأنت تريد التأكد تمامًا من أن مجلد prod لن يتم نشره إلى مستودعك.

داخل مجلد prod، أنشئ ملف validEmails.csv. سيستخدم تطبيقنا هذا الملف لقراءة قائمة البريد الإلكتروني. قم بتهيئة الملف بالمحتويات التالية (استبدل your@email.com بعنوان بريدك الإلكتروني الفعلي):

email,unsubscribeId
your@email.com,1
iama@fake.email,2

الآن يمكننا كتابة الكود لتحليل هذا إلى قائمة بريد إلكتروني! في ملف src/send.ts الخاص بك، أضف هذا الكود:

 // هنا ندمج مسار ملفنا لملف البريد الإلكتروني الصالح
 const filePath = path.join(__dirname + "/../validEmails.csv");

 // هذا هو المكان الذي نبدأ فيه قراءة الملف!
 readFile(filePath, "utf8", (err, data) => {
   if (err) {
     console.error(err);
     return;
   }
   console.log(data);
 });

الآن إذا قمت بتشغيل npm run build و npm run send، يجب أن ترى محتويات ملف validEmail.csv الخاص بنا في الطرفية. إذا أردت، يمكنك عرض تقدمنا الحالي حتى هذه النقطة.

رائع! الآن نحتاج إلى تحليل هذه السلسلة إلى مصفوفة من الكائنات حتى نتمكن من التكرار خلالها وبناء رسائل البريد الإلكتروني الخاصة بنا. قم بتحديث دالة رد الاتصال (callback function) الخاصة بنا:

 // هذا هو المكان الذي نبدأ فيه قراءة الملف!
 readFile(filePath, "utf8", (err, data) => {
   if (err) {
     console.error(err);
     return;
   }
   // هنا نحلل البيانات إلى مصفوفة كائنات
   const emailList = data
     .split("\n")
     .slice(1)
     .map((el) => {
       const [email, unsubscribeId] = el.split(",");
       return { email, unsubscribeId };
     });
 });
  • تقوم الدالة .split("\n") بتقسيم السلسلة حسب فواصل الأسطر. ملاحظة: إذا كنت تستخدم نظام التشغيل Windows، فقد تحتاج إلى تغيير إعداد نهاية السطر لملف validEmails.csv الخاص بك من CRLF إلى LF (يُدخل Windows أحرف فاصل أسطر إضافية ستؤثر على معالجة بياناتنا).
  • تزيل الدالة .slice(1) العنصر الأول من تلك المصفوفة (سطر email,unsubscribeId الخاص بنا).
  • ستقوم دالة map الخاصة بنا بتحويل كل سلسلة email,unsubscribeId إلى كائن {email, unsubscribeId}.

ستكون النتيجة النهائية لدالة التحليل هذه مصفوفة من الكائنات ذات خصائص email و unsubscribeId – وهي أسهل بكثير للتعامل معها من سلسلة نصية.

مثال على مخرجات دالة تحليل البيانات في SendGrid.

مثال على مخرجات دالة التحليل

لقد حان الوقت الآن لإرسال بعض رسائل البريد الإلكتروني. أسفل دالة التحليل الخاصة بك (ولكن لا تزال داخل دالة رد الاتصال readFile)، أضف بنية طريقة التكرار الخاصة بنا. نظرًا لأننا نريد الوصول إلى كل قيمة في المصفوفة، فسنستخدم طريقة .forEach.

 // هنا نكرر عبر مصفوفة emailList
 emailList.forEach((user) => {});

داخل دالة رد الاتصال لـ .forEach، يمكننا إنشاء كائن الرسالة الذي تتوقعه SendGrid API.

 // هنا نكرر عبر مصفوفة emailList
 emailList.forEach((user) => {
   // هذا هو كائن الرسالة الذي يحتاجه SendGrid
   const message: MailDataRequired = {
     to: user.email,
     from: fromAddress,
     subject: subjectValue,
     text: "This goes away!",
     templateId: sgTemplate,
     dynamicTemplateData: {
       subject: subjectValue,
       unsubscribeId: user.unsubscribeId,
     },
   };
 });

قبل المضي قدمًا، دعنا نلقي نظرة فاحصة على كائن الرسالة هذا. يتم استخدام MailDataRequired الذي استوردناه سابقًا كتعريف نوع هنا، لذا يمكن لـ TypeScript تنبيهنا إذا فاتنا خاصية مطلوبة. لحسن الحظ، لدينا جميع الخصائص المطلوبة. ولكن ماذا تعني؟

  • to: عنوان البريد الإلكتروني لإرسال الرسالة إليه. سيكون هذا هو email من كل سطر في ملف validEmails.csv الخاص بنا.
  • from: عنوان البريد الإلكتروني لإرسال الرسالة منه. تم تعيين هذا في ملف .env الخاص بنا سابقًا (يجب أن يكون عنوان بريدك الإلكتروني).
  • subject: هذا الحقل غير مطلوب، ولكنه يمنحنا قيمة احتياطية في حال لم يقم القالب الديناميكي بتحليل موضوعنا بشكل صحيح.
  • text: يتم تجاوز قيمة النص هذه بواسطة القالب. ومع ذلك، لا يزال من المهم استخدامها. يمكن لـ SendGrid إرسال رسائل بريد إلكتروني كنص عادي (plaintext) أو html – باستخدام خاصية text بدلاً من خاصية html، نضمن إرسال قالبنا كنص عادي. من المرجح أن يقوم موفرو البريد الإلكتروني بوضع علامة على رسائل HTML كبريد عشوائي (spam)، لذا يساعد هذا في زيادة معدل التسليم لدينا.
  • templateId: هذا هو معرف القالب الديناميكي الذي يجب أن يستخدمه SendGrid في البريد الإلكتروني.
  • dynamicTemplateData: هذه هي القيم التي تتوافق مع سلاسل Handlebars التي قمنا بتعيينها في القالب الديناميكي سابقًا.

رائع! خطوتنا التالية هي أخذ هذه الرسالة المنشأة وإرسالها. أسفل كائن الرسالة (ولكن لا يزال داخل دالة رد الاتصال .forEach)، دعنا نضيف استدعاء الإرسال الخاص بنا:

 // هنا نرسل الرسالة التي أنشأناها للتو!
 sgMail.send(message);

سيؤدي هذا إلى إرسال الرسالة إلى كل عنوان بريد إلكتروني في ملف validEmails.csv الخاص بنا. لسوء الحظ، سيتم تشغيل الكود الخاص بنا بصمت ولن نعرف ما إذا كان كل إرسال ناجحًا أم لا. دعنا نضيف بعض معالجة الأخطاء. تُرجع مكالمة .send() وعدًا (Promise)، لذا يمكننا استخدام .then().catch() للتعامل مع الإرجاع.

 // هنا نرسل الرسالة التي أنشأناها للتو!
 sgMail
   .send(message)
   .then(() => {
     // هنا نسجل طلبات الإرسال الناجحة
     console.info(`Message send success: ${user.email}`);
   })
   .catch((err) => {
     // هنا نسجل طلبات الإرسال التي حدث بها خطأ
     console.error(err);
     console.error(`Message send failed: ${user.email}`);
   });

الآن إذا قمت بتشغيل npm run build و npm run send، يجب أن ترى بريدًا إلكترونيًا جميلًا في صندوق الوارد الخاص بك! في هذه المرحلة، أصبح لديك الآن تطبيق إرسال بريد إلكتروني وظيفي. تهانينا! يمكنك عرض تقدمنا حتى هذه النقطة إذا أردت. تابع القراءة لترى كيفية التعامل مع رسائل البريد الإلكتروني المرتدة (bounced emails) والمنطق الإضافي لفشل الإرسال، وهو ما سنناقشه بعد ذلك.

التعامل مع رسائل البريد الإلكتروني المرتدة في SendGrid

ربما لاحظت أن iama@fake.email ليس عنوان بريد إلكتروني حقيقي على الإطلاق. سيقوم SendGrid بإنشاء تقارير الارتداد يوميًا لنشاطك في اليوم السابق. كل بريد إلكتروني مرتد يضر بسمعة SendGrid الخاصة بك ويمكن أن يؤدي إلى قيام موفري البريد الإلكتروني بوضع علامة على بريدك كبريد عشوائي. لذلك، نحتاج إلى إضافة منطق لمنع الإرسال إلى العناوين المرتدة المعروفة.

ابدأ بإنشاء ملف bouncedEmails.csv في مجلد prod الخاص بك (يجب أن يكون بجوار ملف validEmails.csv الخاص بك). لا نحتاج إلى قيم unsubscribeId هنا، لذا قم بتهيئته بما يلي:

email
iama@fake.email

الآن نعود إلى ملف send.ts الخاص بنا. في السطر 38، أسفل إعلان filePath الحالي مباشرةً، قم بتهيئة المسار لملف bouncedEmails.csv الجديد.

 // هنا ندمج مسارات ملفاتنا لملفات CSV
 const filePath = path.join(__dirname + "/validEmails.csv");
 const bouncePath = path.join(__dirname + "/bouncedEmails.csv");

رائع! الآن نحتاج إلى قراءة هذا الملف. مباشرةً أسفل إعلانات مسار الملف هذه (قبل استدعاء readFile الحالي)، أضف المنطق لقراءة الملفات المرتدة.

 // قراءة قائمة الارتداد، وتحليلها إلى مصفوفة
 readFile(bouncePath, "utf8", (err, data) => {
   if (err) {
     console.error(err);
     process.exit(1);
   }
   bounceList = data.split("\n").slice(1);

   // ... هنا يأتي منطق الإرسال الحالي الخاص بنا ...
 });

دالة readFile غير متزامنة (asynchronous) – لذلك نحتاج إلى تضمين دالة رد الاتصال حول كل منطق الإرسال الحالي الخاص بنا. تأكد من نقل القوسين }) الختاميين لدالة رد الاتصال هذه إلى نهاية ملفنا تمامًا. نقرأ ملف bouncedEmails.csv، ونقسمه على سطر جديد (تذكر أنك ستحتاج إلى التأكد من أن نهايات الأسطر هي LF)، ونزيل سطر email. أخيرًا، نواصل منطق الإرسال الحالي الخاص بنا.

نعود إلى منطق الإرسال الخاص بنا. داخل دالة .forEach الخاصة بنا، أضف منطقًا لتخطي رسائل البريد الإلكتروني المحظورة (سنضيف هذا قبل إنشاء كائن الرسالة لتجنب إنشاء متغيرات غير ضرورية).

 // هنا نكرر عبر مصفوفة emailList
 emailList.forEach((user) => {
   // هنا نتحقق مما إذا كان البريد الإلكتروني قد ارتد
   if (bounceList.length && bounceList.includes(user.email)) {
     console.info(`Message send skipped: ${user.email}`);
     return;
   }
   // ... هنا يأتي بناء كائن الرسالة ومنطق الإرسال ...
 });

من خلال الاستفادة من عبارة return المبكرة، ننهي تكرار .forEach هذا عندما تتضمن bounceList هذا البريد الإلكتروني. هذا يمنعنا من محاولة الإرسال إلى عناوين البريد الإلكتروني التي ارتدت سابقًا.

الآن إذا قمت بتشغيل npm run build و npm run start، يجب أن ترى هذا الإخراج في طرفيتك:

مثال على مخرجات الطرفية لبريد إلكتروني تم تخطيه وبريد إلكتروني ناجح.

مثال على مخرجات الطرفية لبريد إلكتروني تم تخطيه وبريد إلكتروني ناجح

عرض تقدمنا حتى هذه النقطة.

التقاط رسائل البريد الإلكتروني الفاشلة في SendGrid

حاليًا، سيسجل تطبيقنا خطأ إذا فشل إرسال بريد إلكتروني. يمكن أن يعمل هذا لحالات الاستخدام الصغيرة، ولكن مع توسيع نطاق تطبيقك، ستجد صعوبة متزايدة في تحديد تلك الإخفاقات ومحاولة الإرسال مرة أخرى. بدلاً من ذلك، يمكننا جعل تطبيقنا يحفظ تلك الرسائل الإلكترونية في ملف جديد.

أنشئ ملف failedEmails.csv في مجلد prod الخاص بك. يمكن أن يكون هذا الملف فارغًا. سنكتب الكود لإضافة صف العنوان.

نعود إلى ملف send.ts الخاص بنا، توجه إلى إعلانات المسار الخاصة بنا في السطر 38. دعنا نضيف واحدًا آخر لملف failedEmails.csv الجديد الخاص بنا:

 // هنا ندمج مسارات ملفاتنا لملفات CSV
 const filePath = path.join(__dirname + "/validEmails.csv");
 const bouncePath = path.join(__dirname + "/bouncedEmails.csv");
 const failedPath = path.join(__dirname + "/failedEmails.csv");

على عكس مساراتنا الأخرى، سيتم استخدام هذا المسار لعملية كتابة (write operation). نظرًا لأننا نريد الكتابة باستمرار أثناء معالجة رسائل البريد الإلكتروني، نحتاج إلى إنشاء دفق (stream) للقيام بذلك. مباشرةً أسفل إعلانات المسار هذه، دعنا ننشئ هذا الدفق ونضيف صف العنوان الأولي الخاص بنا.

 // هنا ننشئ دفق الكتابة الخاص بنا لرسائل البريد الإلكتروني الفاشلة
 const failedStream = createWriteStream(failedPath);
 // هنا نضيف صف العنوان
 failedStream.write("email,unsubscribeId\n");

حان الوقت لتحسين منطق معالجة الأخطاء لدينا لدمج هذا الدفق الجديد. نحتاج إلى إضافة عملية كتابة أخرى إلى معالجة الأخطاء الخاصة بنا في استدعاء send.

 // هنا نرسل الرسالة التي أنشأناها للتو!
 sgMail
   .send(message)
   .then(() => {
     // هنا نسجل طلبات الإرسال الناجحة
     console.info(`Message send success: ${user.email}`);
   })
   .catch((err) => {
     // هنا نسجل طلبات الإرسال التي حدث بها خطأ
     console.error(err);
     console.error(`Message send failed: ${user.email}`);
     // وهنا نضيف هذا البريد الإلكتروني إلى failedEmails.csv
     failedStream.write(`${user.email},${user.unsubscribeId}\n`);
   });

سيؤدي هذا إلى كتابة email و unsubscribeId إلى ملف failedEmails.csv الجديد الخاص بنا بالتنسيق الصحيح – مما يسمح لنا بنسخ تلك البيانات إلى validEmails.csv لإجراء محاولة إرسال أخرى. تهانينا! لقد قمت الآن ببناء أداة ناجحة وعملية بالكامل لإرسال رسائل البريد الإلكتروني بكميات كبيرة. يمكنك رؤية الكود الكامل إذا كنت ترغب في تأكيد عملك. ولكن تابع القراءة للحصول على بعض الميزات الاختيارية “الجميلة”.

ميزات اختيارية لأداة البريد الإلكتروني الخاصة بك

نظرًا لأن أداتنا تعتمد على واجهة سطر الأوامر (CLI-based) (مما يعني أنها تُستخدم في واجهة سطر الأوامر، أو الطرفية)، فلا يوجد الكثير من ملاحظات المستخدم. يمكننا الاستفادة من بعض وظائف الطرفية الإضافية لتوفير المزيد من المعلومات حول تقدم السكربت. دعنا نبدأ بإضافة بعض “نقاط التفتيش” (checkpoints).

قبل التحقق من متغيرات البيئة الخاصة بنا، دعنا نطبع رسالة تفيد بأن السكربت قد بدأ ويتحقق من المتغيرات:

 console.info('Script started. Validating environment variables...');

بعد ذلك، بعد التحقق، يمكننا طباعة رسالة نجاح.

 // هنا نعيّن مفتاح SendGrid API
 sgMail.setApiKey(apiKey);
 console.info('Variables confirmed!');

داخل دالتنا لقراءة الملف المرتد، يمكننا إضافة بعض الرسائل للبدء والفشل والنجاح.

 console.info('Reading bounced email list...');
 // قراءة قائمة الارتداد، وتحليلها إلى مصفوفة
 readFile(bouncePath, "utf8", (err, data) => {
   if (err) {
     console.error(err);
     console.error('Failed to read bounced emails!');
     process.exit(1);
   }
   bounceList = data.split("\n").slice(1);
   console.info('Bounced emails read!');
   // ... منطق الإرسال الرئيسي يذهب هنا ...
 });

والشيء نفسه ينطبق على قائمة البريد الإلكتروني الصالحة لدينا:

 console.info('Reading send list...');
 // هذا هو المكان الذي نبدأ فيه قراءة الملف!
 readFile(filePath, "utf8", (err, data) => {
   if (err) {
     console.error(err);
     console.error('Failed to read send list!');
     return;
   }
   // ... بقية منطق قراءة الملف والتحليل ...
 });

الآن، سيكون من الجيد جدًا أن يتم طباعة رسالة عند اكتمال العملية. ومع ذلك، إذا أضفنا console.info بعد حلقة .forEach الخاصة بنا، فسيتم طباعتها في الواقع قبل انتهاء إرسال رسائل البريد الإلكتروني! هذا لأن طريقة .send تنشئ استدعاء شبكة (network call) وتُرجع وعدًا (Promise)، وقد لا يكون هذا الوعد قد تم حله/رفضه قبل انتهاء التكرار لدينا.

لذلك بدلاً من ذلك، يمكننا بناء عداد لتتبع عدد رسائل البريد الإلكتروني التي أرسلناها مقابل العدد الإجمالي لرسائل البريد الإلكتروني. قبل حلقة .forEach الخاصة بنا مباشرةً، أضف هذه المتغيرات:

 // هنا ننشئ متغيرات للعد
 const emailTotal = emailList.length;
 let emailCount = 0;

نريد أن نحسب رسائل البريد الإلكتروني المرتدة كرسائل تمت معالجتها، حتى لو كنا نتخطاها.

 // هنا نكرر عبر مصفوفة emailList
 emailList.forEach((user) => {
   // هنا نتحقق مما إذا كان البريد الإلكتروني قد ارتد
   if (bounceList.includes(user.email)) {
     console.info(`Message send skipped: ${user.email}`);
     emailCount++;
     if (emailCount === emailTotal) {
       console.info(`Sending complete! Sent ${emailTotal} emails. Have a nice day!`);
       return;
     }
     return;
   }
   // ... بقية منطق الإرسال ...
 });

أخيرًا، نحتاج إلى إضافة منطق لمعرفة ما إذا كان البريد الإلكتروني الذي أرسلناه هو آخر بريد إلكتروني. يذهب هذا المنطق في معالجات النجاح والفشل لاستدعاء الإرسال:

 // هنا نرسل الرسالة التي أنشأناها للتو!
 sgMail
   .send(message)
   .then(() => {
     // هنا نسجل طلبات الإرسال الناجحة
     console.info(`Message send success: ${user.email}`);
     // هنا نتعامل مع عدد رسائل البريد الإلكتروني
     emailCount++;
     if (emailCount === emailTotal) {
       console.info(`Sending complete! Sent ${emailTotal} emails. Have a nice day!`);
     }
   })
   .catch((err) => {
     // هنا نسجل طلبات الإرسال التي حدث بها خطأ
     console.error(err);
     console.error(`Message send failed: ${user.email}`);
     // وهنا نضيف هذا البريد الإلكتروني إلى failedEmails.csv
     failedStream.write(`${user.email},${user.unsubscribeId}\n`);
     // هنا نتعامل مع عدد رسائل البريد الإلكتروني
     emailCount++;
     if (emailCount === emailTotal) {
       console.info(`Sending complete! Sent ${emailTotal} emails. Have a nice day!`);
     }
   });

وبهذا، يكون تطبيقنا قد اكتمل تمامًا! إذا قمت بتشغيل سكربتات npm run build و npm run send، يجب أن ترى هذا الإخراج في طرفيتك:

مثال على مخرجات الطرفية لتطبيق مكتمل بنجاح.

مثال على مخرجات الطرفية لتطبيق مكتمل

ويجب أن تكون قد تلقيت بعض رسائل البريد الإلكتروني التي تبدو مشابهة لهذا:

صورة عينة لرسالة بريد إلكتروني اختبارية تم إرسالها بنجاح.

صورة عينة لنتيجة البريد الإلكتروني التجريبي

الخلاصة التقنية

لقد قمنا في هذا المقال برحلة شاملة لبناء أداة إرسال رسائل إخبارية قوية ومرنة باستخدام SendGrid API و Node.js. لم نكتفِ بإنشاء تطبيق وظيفي فحسب، بل ركزنا أيضًا على دمج أفضل الممارسات في مجال البريد الإلكتروني، مثل توثيق النطاق (SPF، DKIM، DMARC) وإدارة عناوين IP المخصصة، وهي عناصر حاسمة لضمان معدلات تسليم عالية وحماية سمعة المرسل. إن القدرة على التعامل مع الرسائل المرتدة وتسجيل الإخفاقات بشكل منهجي تضمن استمرارية حملاتك البريدية وتحسينها بمرور الوقت. هذه الأداة، بفضل مرونتها وقدرتها على استخدام القوالب الديناميكية، توفر أساسًا ممتازًا لأي مطور يسعى لأتمتة عمليات التواصل عبر البريد الإلكتروني بكفاءة واحترافية.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *