استضافة صندوق بريد مؤقت بـ Mailpit على VPS
التقط كل رسائل الاختبار على نطاق مؤقت باستخدام Mailpit، واقرأها من واجهة الويب، وامنع تطبيق staging من مراسلة العملاء الحقيقيين نهائياً.
ما هو صندوق البريد الإلكتروني المؤقت
صندوق البريد الإلكتروني المؤقت هو خادم SMTP (بروتوكول نقل البريد البسيط) صغير يقبل الرسائل لأي عنوان، لكنه لا يسلّم أياً منها. يرسل إليه تطبيق staging بدلاً من إرسالها إلى مزود بريد حقيقي، وتتوقف كل رسالة فيه. تقرأ الرسائل التي وصلت عبر واجهة ويب، لذلك لا تكلّفك قائمة مستلمين خاطئة أو قالب معطّل شيئاً، لأن الرسالة لا تغادر الصندوق.
يبني هذا الدليل صندوقاً واحداً على VPS باستخدام Docker Compose. يعمل Mailpit كمستودع شامل للرسائل. يستمع SMTP الخاص به على عنوان لا يستطيع الوصول إليه إلا تطبيقك، بينما توجد واجهة الويب خلف nginx مع أمان طبقة النقل (TLS) وكلمة مرور. ويمنع حد الاحتفاظ الرسائل من ملء القرص. إذا كان Compose جديداً عليك، يشرح دليل أساسيات Compose على VPS بنية الملفات التي يفترضها هذا الدليل.
النتيجة أداة اختبار وليست خادم بريد. لا تحتوي على حسابات، ولا تسليم للرسائل، ولا تصفية للرسائل المزعجة. أما صناديق البريد الحقيقية للمستخدمين الحقيقيين فتتطلب خادماً كاملاً للبريد مثل Mailcow، وهي مهمة أكبر بكثير.
Mailpit مقابل Inbucket مقابل MailHog: أي مُلتقِط تشغّل
تؤدي ثلاث أدوات هذه المهمة. ويكمن الفرق بينها في حالة صيانتها، والمنافذ التي تستمع إليها، وما يمكنها فعله بالرسالة بعد قبولها. تم التحقق من الإصدارات أدناه في August 2026.
MailHog (mailhog/mailhog) يستمع على المنفذ 1025 لـSMTP، ويعرض واجهته على المنفذ 8025. وما يزال يعمل. لم يتضمن فرعه الافتراضي أي commit منذ August 2022، ويحتوي متعقّب المشكلات على أكثر من 250 مشكلة مفتوحة، لذلك ستشغّل dependencies غير مُرقّعة ضمن مسار الاختبار لديك. لا تبدأ أعمالاً جديدة باستخدامه.
Inbucket (inbucket/inbucket) يستمع على المنفذ 2500 لـSMTP، وعلى المنفذ 9000 لواجهة الويب، وعلى المنفذ 1100 لـPOP3 (بروتوكول مكاتب البريد الإصدار 3). صدر الإصدار 3.1.1 في December 2025. يخزّن الرسائل كملفات ضمن /storage ويحذفها تلقائياً وفق سياسة التنظيف الخاصة به: تضبط الصورة INBUCKET_STORAGE_RETENTIONPERIOD=72h وINBUCKET_STORAGE_MAILBOXMSGCAP=300. اختره عندما يحتاج الاختبار إلى جمع البريد باستخدام مكتبة عميلة لـPOP3 بدلاً من استدعاء HTTP.
Mailpit (axllent/mailpit) يستخدم المنافذ نفسها التي يستخدمها MailHog، وهما 1025 و8025، لذلك يمكنك استبدال MailHog من دون تعديل إعدادات التطبيق. صدر الإصدار 1.30.7 في 8 August 2026. ويتضمن داخل binary كل ما يحتاج إليه هذا الدليل: ملف password لواجهة الويب وAPI (واجهة برمجة التطبيقات)، وحداً أقصى لعدد الرسائل، وحداً أقصى لعمرها، ومرشحاً للمستلمين. يستخدم باقي هذا الدليل Mailpit.
آلية catch-all، ولماذا لا علاقة لـDNS بالأمر
لا يبحث تطبيقك هنا عن وجهة التسليم. أنت تمرّر إليه مضيفاً ومنفذاً، فيفتح اتصال TCP ويعلن RCPT TO:<anyone@example.test>. يقبل Mailpit ذلك المستلم مهما كانت قيمته، ويخزّن الرسالة ولا يرسلها إلى أي جهة. لا يُجرى تحليل اسم النطاق مطلقاً، لذلك يعمل example.test رغم أن .test اسم محجوز لا وجود له في نظام أسماء النطاقات (DNS).
هذه هي الآلية كاملة، ولهذا تكون علبة الوارد آمنة افتراضياً. لا يُستخدم أي سجل MX (mail exchanger)، ولا تُجرى أي محاولة تسليم، ولا يمكن لأي رسالة أن تصل إلى شخص حقيقي.
وجّه تطبيق الاختبار إلى خادم الالتقاط
اضبط مضيف SMTP في التطبيق على mailpit عندما يعمل التطبيق كحاوية ضمن مشروع Compose نفسه، أو على 127.0.0.1 عندما يعمل على الخادم المضيف. اضبط المنفذ على 1025، وعطّل TLS، واترك اسم المستخدم وكلمة المرور فارغين. يقبل Mailpit الرسائل المرسلة دون مصادقة.
ترفض بعض أطر العمل الإرسال من دون بيانات اعتماد. يتيح MP_SMTP_AUTH_ACCEPT_ANY=1 لـMailpit قبول أي اسم مستخدم وكلمة مرور، ويسمح MP_SMTP_AUTH_ALLOW_INSECURE=1 باستخدام آليتي PLAIN وLOGIN عبر اتصال غير مشفّر. يكون هذان الإعدادان آمنين هنا فقط لأن المستمع غير قابل للوصول من الإنترنت، وهو ما يفرضه النشر أدناه.
يستحسن ضبط MP_SMTP_ALLOWED_RECIPIENTS منذ اليوم الأول. يتلقى هذا الإعداد تعبيراً نمطياً ويرفض كل مستلم لا يطابقه. وجّهه إلى نطاق الاختبار، وعند احتواء قاعدة بيانات الاختبار على عنوان حقيقي لأحد العملاء، سيظهر فشل واضح في سجل التطبيق بدلاً من وصول الرسالة بصمت إلى خادم الالتقاط.
ملف Docker Compose
أنشئ الدليل وملف كلمات المرور لواجهة الويب أولاً. يكتب htpasswd -B تجزئة bcrypt، كما يقرأ Mailpit قيم bcrypt والنص العادي.
mkdir -p ~/mailpit/data
cd ~/mailpit
sudo apt update && sudo apt install -y apache2-utils
htpasswd -B -c data/ui-auth qaاكتب compose.yaml:
services:
mailpit:
image: axllent/mailpit:v1.30
container_name: mailpit
restart: unless-stopped
ports:
- "127.0.0.1:8025:8025"
- "127.0.0.1:1025:1025"
volumes:
- ./data:/data
environment:
MP_DATABASE: /data/mailpit.db
MP_MAX_MESSAGES: 2000
MP_MAX_AGE: 14d
MP_UI_AUTH_FILE: /data/ui-auth
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
MP_SMTP_ALLOWED_RECIPIENTS: '@example\.test$$'علامة الدولار المزدوجة ليست خطأ مطبعياً. يقرأ Compose علامة $ واحدة باعتبارها بداية متغير لتوسيعه، لذلك تمثل $$ طريقة تمرير علامة دولار حرفية واحدة إلى الحاوية. يصل التعبير النمطي إلى Mailpit بالشكل @example\.test$.
شغّل الحاوية وتحقق من حالة الصحة:
docker compose up -d
docker compose psيجب أن يعرض العمود STATUS القيمة Up ... (healthy). تتضمن الصورة فحص صحة خاصاً بها يشغّل /mailpit readyz كل 15 ثانية، لذلك فإن بقاء الحاوية في الحالة starting أو تحوّلها إلى unhealthy يعني أنها لا تخدم على المنفذ 8025 داخل الحاوية. اقرأ docker compose logs mailpit قبل تغيير أي شيء آخر.
يتضمن كل منفذ منشور عنواناً، وهذا العنوان هو عنصر التحكم الأمني. يستمع Mailpit داخل الحاوية على 0.0.0.0، وهذا مناسب لأن للحاوية مساحة أسماء شبكية خاصة بها. يحدد الجانب الأيسر من الربط من يمكنه الوصول إليها من الخارج. اكتب 8025:8025، وسيعمل Docker على ربط كل عنوان على الخادم، بما في ذلك العنوان العام.
إذا كان تطبيق staging لديك خدمةً في هذا الملف نفسه، فاحذف ربط 1025 بالكامل ووجّه التطبيق إلى اسم المضيف mailpit على المنفذ 1025. تصل الحاويات الموجودة على شبكة Compose مشتركة إلى بعضها مباشرة، لذلك لا يصل منفذ SMTP إلى الخادم المضيف إطلاقاً. يشرح كيفية حل شبكات Compose لأسماء الخدمات عملية البحث هذه.
أرسل رسالة واحدة وتحقق من وصولها
python3 - <<'EOF'
import smtplib
from email.message import EmailMessage
m = EmailMessage()
m["From"] = "staging@example.test"
m["To"] = "anyone@example.test"
m["Subject"] = "Mailpit smoke test"
m.set_content("If this appears in the web interface, the sink works.")
with smtplib.SMTP("127.0.0.1", 1025) as s:
s.send_message(m)
EOFلا يطبع البرنامج النصي شيئاً عند نجاحه. أكّد تخزين الرسالة عبر API:
curl -s -u qa:yourpassword http://127.0.0.1:8025/api/v1/messagesيعرض ذلك JSON يتضمن الرسائل المخزّنة. احذف الخيار -u، وسيُرفض الطلب نفسه لأن MP_UI_AUTH_FILE يحمي API وواجهة الويب معاً. يجب أن يرسل أي اختبار يقرأ صندوق الوارد بيانات الاعتماد نفسها أيضاً.
يعني ظهور ConnectionRefusedError من برنامج Python النصي عدم وجود أي خدمة تستمع على 127.0.0.1:1025. هذه هي النتيجة المتوقعة إذا أزلت ربط SMTP، وعندها يجب تشغيل الاختبار من حاوية على شبكة Compose نفسها.
انشر واجهة الويب عبر nginx باستخدام كلمة مرور
تستجيب الواجهة حالياً على عنوان loopback فقط. ينهي nginx اتصال TLS ويطلب كلمة مرور قبل وصول أي شيء إليها.
sudo htpasswd -B -c /etc/nginx/mailpit.htpasswd qaserver {
listen 443 ssl;
server_name mail-test.example.com;
ssl_certificate /etc/letsencrypt/live/mail-test.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail-test.example.com/privkey.pem;
auth_basic "mailpit";
auth_basic_user_file /etc/nginx/mailpit.htpasswd;
location / {
proxy_pass http://127.0.0.1:8025;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}أعد التحميل بعد فحص الصياغة باستخدام sudo nginx -t && sudo systemctl reload nginx. يستحق شرح وظيفة كل توجيه في كتلة reverse proxy القراءة مرة واحدة إذا كانت هذه أول مرة تستخدم فيها proxy.
استخدم اسم المستخدم وكلمة المرور نفسيهما في ملف nginx وفي data/ui-auth. يمرر nginx ترويسة Authorization الخاصة بالمتصفح إلى الخدمة الخلفية، لذلك تؤدي بيانات الاعتماد المتطابقة إلى استيفاء عمليتي التحقق من خلال مطالبة واحدة. أما استخدام بيانات اعتماد مختلفة فيترك المتصفح محتفظاً بمجموعة واحدة ترفضها عملية التحقق الثانية.
ليست الترويستان Upgrade وConnection للزينة. يرسل Mailpit البريد الجديد إلى صفحة مفتوحة عبر WebSocket، ولا يستطيع proxy الذي يعمل باستخدام HTTP/1.1 من دون هاتين الترويستين ترقية الاتصال. عندها تُحمّل الصفحة بشكل صحيح، لكنها لا تتغير أبداً: يصل البريد، وتعرضه API، وتبقى القائمة ثابتة إلى أن تعيد تحميل الصفحة.
أبقِ آليتي الحماية. تحمي كلمة مرور nginx العنوان العام، بينما تحمي MP_UI_AUTH_FILE المنفذ 8025 نفسه. وهذا مهم لأن كل رابط لإعادة تعيين كلمة المرور أنشأه تطبيق staging لديك على الإطلاق يمكن قراءته في تلك الواجهة.
لا تسمح مطلقاً بأن تتحول خدمة sink إلى open relay
خادم open relay هو خادم SMTP يستقبل رسالة من أي جهة ويمررها إلى أي وجهة. يفحصه مرسلو الرسائل المزعجة باستمرار، والعثور على واحد ضمن عناوينك يؤدي إلى بلاغات إساءة استخدام وتعليق الحساب.
لا يعمل Mailpit كـopen relay افتراضياً، لأنه لا يمرر الرسائل مطلقاً. يظل التمرير متوقفاً إلى أن توجّه MP_SMTP_RELAY_CONFIG إلى ملف إعدادات relay، كما أن إجراء release في الواجهة لا يفعل شيئاً قبل ذلك. ترك هذا الإعداد غير محدد خيار مقصود.
يمكن أن تفقد هذه الخاصية بطريقتين. إذا أعددت relay كي يعمل زر release، ثم عرّضت منفذ SMTP للإنترنت، فقد أنشأت open relay فعالاً. وإذا عرّضت المنفذ من دون relay، فلن يتمكن الغرباء من إرسال البريد عبرك، لكنهم يستطيعون ملء مساحة التخزين وإدخال محتوى إلى الواجهة التي يثق بها فريقك.
المشكلة الخفية على مضيف Docker هي جدار الحماية. يؤدي نشر منفذ إلى جعل Docker يكتب قواعده الخاصة في جدول nat، وتتم مطابقة حركة المرور المتجهة إلى الحاوية هناك قبل أن تتمكن قواعد ufw (uncomplicated firewall) من التأثير. يبلّغ sudo ufw deny 1025/tcp عن نجاح العملية من دون أن يغيّر شيئاً. يشرح سبب نشر Docker للمنافذ متجاوزاً ufw مباشرة ترتيب السلسلة.
الحل هو العنوان المحدد في الربط، وليس قاعدة في جدار الحماية. تحقّق مما يرتبط به المنفذ فعلياً:
sudo ss -ltnp | grep -E ':(1025|8025)'يُظهر الناتج السليم 127.0.0.1:1025 و127.0.0.1:8025. ويعني السطر الذي يقرأ 0.0.0.0:1025 أن الربط فقد عنوانه، وأن خدمة sink تستمع إلى الإنترنت. ومن جهاز آخر، يجب أن تنتهي مهلة nc -vz mail-test.example.com 1025 أو يُرفض الاتصال.
عندما يكون التطبيق على خادم مختلف، لا تفتح 1025 لربط الجهازين. ضع الجهازين على شبكة خاصة أو نفق VPN، واربط التعيين بعنوان واجهة تلك الشبكة.
لا تنشر سجلات MX إلا إذا كنت تريد استقبال بريد فعلي
يُخبر سجل MX (mail exchanger) خوادم البريد الأخرى بالمضيف الذي يقبل البريد لنطاق معيّن. إذا لم يتضمن نطاقك المؤقت سجل MX، فلن تصل إليه أي رسائل من الإنترنت، لأن خوادم الإرسال لا تجد وجهة لتسليمها. لن يحتوي صندوق الوارد إلا على الرسائل التي أرسلتها تطبيقاتك بنفسها، وهذا هو الغرض من صندوق بريد الاختبار.
يعني استقبال البريد الفعلي وجود سجل MX يشير إلى الخادم، واستماع Mailpit على المنفذ 25 (MP_SMTP_BIND_ADDR=0.0.0.0:25)، وفتح هذا المنفذ. عندها ستشغّل نقطة استقبال عامة شاملة لكل عنوان في النطاق. انتبه إلى ما يترتب على ذلك.
- يبدأ البريد المزعج خلال أيام من ظهور السجل، لأن أدوات جمع العناوين تقرأ DNS. ثم تنفّذ هجمات القاموس على الأسماء الشائعة، وتخزّن رسالة لكل محاولة.
- تصل المرفقات من جهات مجهولة إلى قرصك وتبقى عليه. لا يوجد ما يرشّحها، لذلك قد يبقى أرشيف من مرسل مجهول بجانب رسائل الاختبار الخاصة بك.
- يستطيع أي شخص يعرف النطاق التسجيل في خدمات تابعة لجهات خارجية باستخدام عنوان فيه، وستُسلَّم رسالة التأكيد إلى خادمك. وإذا تمكّن أحدهم يوماً من تجاوز بوابة كلمة المرور، فستصبح تلك الحسابات متاحة لمن يقرأ صندوق الوارد.
- تتوقف حدود الاحتفاظ عن كونها إجراءً تنظيمياً، وتصبح ضرورية لتحمل الحمل، لأنك لم تعد تتحكم في حجم البريد.
إذا احتجت إلى استقبال بريد فعلي للتحقق من قابلية التسليم، فخصّص له نطاقاً فرعياً، واجعل مدة الاحتفاظ MP_MAX_AGE قصيرة، وتعامل مع كل ما يرد إليه على أنه عام. وإذا احتجت إلى صناديق بريد يعتمد عليها المستخدمون، فشغّل خادم بريد فعلياً مع التصفية والنسخ الاحتياطية بدلاً من ذلك.
الاحتفاظ: كيف يملأ إعداد الالتقاط الشامل غير المقيّد القرص
يحتفظ Mailpit بـ500 رسالة افتراضياً، ويحذف دورياً الرسائل الأقدم التي تتجاوز هذا العدد. يعطّل MP_MAX_MESSAGES: 0 الحذف التلقائي بالكامل، وهذا التغيير وحده هو ما يجعل إعداد الالتقاط الشامل يملأ القرص من دون أن يلاحظ أحد. يضيف MP_MAX_AGE حداً زمنياً، ويستغرق تطبيقه ساعات أو أياماً، وتُكتب قيمته بصيغة 36h أو 14d.
يحدد MP_DATABASE ما إذا كانت هذه البيانات ستبقى. من دونه، يكتب Mailpit إلى ملف مؤقت يُحذف عند خروج العملية، ولذلك يؤدي كل تشغيل إلى إفراغ صندوق الوارد. ومعه، تبقى الرسائل بعد إعادة التشغيل ويستمر الملف في النمو.
تستهلك المرفقات معظم المساحة. إذا أرسلت مهمة ليلية تقرير PDF بحجم 2 MB إلى 300 عناوين اختبار، فستستهلك 600 MB كل ليلة، ولن يتفاعل حد عدد الرسائل وحده في الوقت المناسب. احسب هذا النمو ضمن سعة التخزين التي تتشاركها الخدمات الأخرى، لأن خدمة مجاورة تستهلك الوسائط بكثافة، مثل PhotoPrism أو Immich، ستكون قد حجزت معظم مساحة قرص VPS صغير.
du -h ~/mailpit/data/mailpit.db
df -h /أفرغ مخزن الرسائل بين عمليات CI بدلاً من انتظار تفعيل الحد:
curl -s -u qa:yourpassword -X DELETE http://127.0.0.1:8025/api/v1/messagesيعالج Inbucket المشكلة نفسها باستخدام INBUCKET_STORAGE_RETENTIONPERIOD (72h في الصورة) وINBUCKET_STORAGE_MAILBOXMSGCAP (300). أياً كانت الأداة التي تشغّلها، حدّد الحد قبل أن توجّه أول مجموعة اختبارات إليه.
قراءة صندوق البريد الوارد من مجموعة الاختبارات
يعرض GET /api/v1/messages العناصر المخزنة، ويعيد GET /api/v1/message/{ID} رسالة واحدة مع أجزائها ورؤوسها، ويصفّي GET /api/v1/search النتائج، ويمسح DELETE /api/v1/messages المخزن. تتوفر الوثائق التفاعلية للإصدار الذي تشغّله على http://127.0.0.1:8025/api/v1/.
يرسل اختبار مفيد رسالة، ثم يستعلم بشكل متكرر حتى تظهر، ويتحقق من الموضوع والرابط الموجود فيها، ثم يحذف كل شيء. استخدم حلقة إعادة محاولة قصيرة للاستعلام بدلاً من إرسال طلب واحد، لأن التطبيق الذي يضع البريد في قائمة انتظار داخل عامل يعمل في الخلفية يعيد التحكم من استدعاء الإرسال قبل أن تصل الرسالة إلى Mailpit. يظهر النمط نفسه في أدوات اختبار ومحاكاة واجهات API المستضافة ذاتياً، وهي عادةً النصف الآخر من بيئة التجهيز التي لا تتصل ببيئة الإنتاج.
FAQ
هل تُعدّ علبة بريد مؤقتة مستضافة ذاتياً مرحِّلاً مفتوحاً؟
لا، ما دام الترحيل معطّلاً. يخزّن Mailpit الرسائل ولا يعيد توجيهها إلى أن توجّه MP_SMTP_RELAY_CONFIG إلى إعدادات مرحّل، لذلك لا يستطيع شخص يصل إلى المنفذ 1025 إرسال البريد عبر خادمك. لكنه يستطيع ملء مساحة التخزين لديك، لذا اربط منفذ SMTP بعنوان لا يمكن إلا لتطبيقك الوصول إليه. نشره على أنه 1025:1025 في Compose يربطه بكل عناوين المضيف، ولن يغلقه sudo ufw deny 1025/tcp، لأن قواعد nat الخاصة بـDocker تُطابق أولاً.
هل أحتاج إلى سجل MX لنطاق الاختبار؟
فقط إذا أردت وصول البريد من الإنترنت. من دون سجل MX، لا تجد خوادم الإرسال مكاناً لتسليم الرسائل، لذلك لا تحتوي علبة البريد إلا على ما ترسله تطبيقاتك عبر SMTP. انشر السجل وافتح المنفذ 25، وستشغّل جامعاً عاماً لكل الرسائل: رسائل مزعجة خلال أيام، وهجمات تخمين تخزّن رسالة لكل محاولة، ومرفقات من أشخاص مجهولين على قرصك من دون أي تصفية.
لماذا لا تُحدّث قائمة الرسائل إلا عندما أعيد تحميل الصفحة؟
يدفع Mailpit الرسائل الجديدة إلى صفحة مفتوحة عبر WebSocket. لا يستطيع مقطع location في nginx الذي يفتقد proxy_http_version 1.1 والرأسين Upgrade وConnection ترقية ذلك الاتصال، لذلك تُحمّل الصفحة بصورة طبيعية ثم تتوقف عن التحديث. يستمر وصول البريد، وتستمر API في إرجاعه، ولذلك تبدو علبة البريد قديمة لا معطّلة. أضف هذه الأسطر، ثم أعد تحميل nginx وأعد تحميل الصفحة.
كيف أوقف امتلاء القرص بعلبة البريد؟
أبقِ MP_MAX_MESSAGES على قيمة فعلية وأضف MP_MAX_AGE. الحد الافتراضي هو 500 رسالة، ويؤدي ضبطه على 0 إلى تعطيل الحذف تماماً، وبذلك ينمو جامع الرسائل العام المزود بالمرفقات بصمت. يقبل MP_MAX_AGE الساعات أو الأيام، مثل 36h أو 14d. امسح مخزن الرسائل أثناء تنظيف CI باستخدام curl -X DELETE http://127.0.0.1:8025/api/v1/messages. ينفّذ Inbucket المهمة نفسها باستخدام INBUCKET_STORAGE_RETENTIONPERIOD (72h) وINBUCKET_STORAGE_MAILBOXMSGCAP (300).
هل أشغّل Mailpit أم Inbucket أم MailHog؟
استخدم Mailpit للمشاريع الجديدة، اعتباراً من August 2026. لا يزال MailHog يعمل، لكن فرعه الافتراضي لم يشهد أي commit منذ August 2022، ولذلك يشحن تبعيات غير مُرقّعة. يخضع Inbucket للصيانة النشطة (3.1.1، December 2025)، وهو الخيار الأفضل عندما يحتاج الاختبار إلى POP3، لأن خادم POP3 في Mailpit لا يبدأ إلا بعد تزويده بملف كلمات مرور. يستخدم Mailpit المنافذ نفسها التي يستخدمها MailHog، وهما 1025 و8025، لذلك لا يتطلب استبدال MailHog سوى تغيير اسم image واحد في ملف Compose.