راهنما · Gmail API

یک تیم محصول چگونه باید Gmail API را به‌صورت امن پیاده‌سازی کند؟

Gmail API را به‌عنوان دسترسی تفویض‌شده به یک صندوق Gmail مشخص پیاده‌سازی کنید، نه به‌عنوان اعتبارنامه‌ای عمومی برای تحویل ایمیل. کوچک‌ترین scope در OAuth را که از قابلیت موردنظر پشتیبانی می‌کند انتخاب کنید، از وضعیت مجوز و refresh tokenها محافظت کنید و هر صندوق را در محدوده tenant خودش نگه دارید. پیام‌ها را با یک کتابخانه بالغ برای پیام‌های اینترنتی بسازید، شناسه پیام Gmail بازگشتی را ثبت کنید و تغییرات را از طریق Pub/Sub به‌همراه سوابق history همگام کنید. جعل هویت با حساب سرویس (service-account impersonation) را تصمیمی برای مدیر Workspace بدانید. در پایان، پذیرش API، تحویل به سرور گیرنده و رسیدن به صندوق ورودی را نتایج جداگانه نگه دارید.

پیش از نوشتن کد، مدل صندوق ایمیل را انتخاب کنید

Gmail API روی صندوق Gmail یک کاربر عمل می‌کند. این API زمانی مناسب است که محصول باید آن صندوق را بخواند، برچسب‌ها و رشته‌های گفتگوی آن را سازمان‌دهی کند، پیش‌نویس بسازد، به‌عنوان کاربر مجاز ارسال کند یا تغییرات صندوق را همگام کند. این اختیار به‌مراتب گسترده‌تر از فراخوانی یک API ایمیل برنامه از یک دامنه محصول تأییدشده است. ابتدا کار دقیق صندوق و کسی را که دسترسی می‌دهد مشخص کنید. یک محصول کاربرمحور معمولاً برای هر حساب Google متصل از رضایت OAuth استفاده می‌کند. یک خودکارسازی داخلی Google Workspace ممکن است به‌جای آن از domain-wide delegation تأییدشده توسط مدیر استفاده کند. اگر تنها نیاز، ارسال رسیدها، لینک‌های تأیید، هشدارها یا سایر پیام‌هایی است که محصول از دامنه تحت کنترل شرکت ارسال می‌کند، کاملاً از دسترسی به صندوق پرهیز کنید و یک API ایمیل تراکنشی را ارزیابی کنید. این تصمیم معماری، پیش از آنکه هیچ کنترل امنیتی یا صفحه رضایتی مجبور به جبران آن شود، دسترسی غیرضروری را کاهش می‌دهد.

محدودترین scope عملی را مجاز کنید

یک OAuth client برای نوع برنامه درست پیکربندی کنید، از یک redirect URI دقیق و ثبت‌شده استفاده کنید و پاسخ مجوز را با یک مقدار state غیرقابل‌پیش‌بینی به نشست مرورگری که آن را آغاز کرده متصل کنید. دسترسی را در بافت مناسب درخواست کنید، یعنی وقتی کاربر قابلیتی را که به آن نیاز دارد فعال می‌کند. برای یکپارچه‌سازی فقط‌ارسال، `https://www.googleapis.com/auth/gmail.send` محدودتر از scopeهایی است که صندوق را می‌خوانند یا تغییر می‌دهند. Google، `gmail.send` را حساس (sensitive) طبقه‌بندی می‌کند، در حالی که scopeهایی مانند `gmail.readonly`، `gmail.compose` و `gmail.modify` محدودشده (restricted) هستند. یک برنامه عمومی که از دسترسی حساس یا محدودشده استفاده می‌کند ممکن است به تأیید OAuth نیاز داشته باشد و ذخیره‌سازی یا انتقال داده‌های scope محدودشده در سمت سرور می‌تواند الزامات ارزیابی امنیتی بیشتری ایجاد کند. دسترسی offline را فقط زمانی درخواست کنید که کار پس‌زمینه واقعاً لازم باشد. refresh tokenها را رمزنگاری کنید، هر token را به یک tenant داخلی و یک subject در Google مرتبط کنید، هرگز آن را در کد مرورگر یا لاگ‌ها قرار ندهید و یک مسیر قطع اتصال آزموده‌شده فراهم کنید که اعتبارنامه‌های محلی را حذف و پردازش پس‌زمینه را متوقف کند.

حساب‌های سرویس و domain-wide delegation را بشناسید

حساب سرویس (service account) یک هویت برنامه است، نه یک صندوق Gmail آماده. به‌تنهایی به پیام‌های کارکنان دسترسی پیدا نمی‌کند. برای داده‌های کاربران Google Workspace، یک super administrator باید شناسه عددی client حساب سرویس و فهرست دقیقی از scopeهای OAuth را صریحاً از طریق domain-wide delegation مجاز کند. سپس برنامه برای یک کاربر مشخص اعتبارنامه تفویض‌شده درخواست می‌کند و هر فراخوانی API با مجوزهای همان کاربر و در محدوده scopeهای مجاز انجام می‌شود. subject جعل‌هویت‌شده را در داده‌های job و لاگ‌های ممیزی صریح نگه دارید تا یک worker پس‌زمینه نتواند بی‌صدا صندوق را عوض کند. برای بارهای کاری اساساً متفاوت از حساب‌های سرویس جداگانه استفاده کنید، وقتی محیط اجرا می‌تواند از اعتبارنامه‌های مدیریت‌شده استفاده کند از کلیدهای خصوصی قابل‌دانلود پرهیز کنید و مجوزهای domain-wide را به‌طور دوره‌ای بازبینی کنید. حساب‌های Gmail شخصی مدیر Workspace ندارند که بتواند این تفویض سازمانی را اعطا کند، بنابراین برای آن حساب‌ها از رضایت OAuth کاربر استفاده کنید.

پیام‌ها را بدون از دست دادن کنترل یا قابلیت ممیزی ارسال کنید

Gmail یک پیام ایمیل اینترنتی کامل را در فیلد `raw`، رمزگذاری‌شده با base64url، از طریق `users.messages.send` می‌پذیرد؛ محصول همچنین می‌تواند یک پیش‌نویس بسازد و بعداً ارسال کند. به‌جای الحاق دستی خطوط هدر، از یک کتابخانه پیام نگهداری‌شده برای تولید ساختار From، To، Cc، Bcc، Subject، Date، Message-ID، متن، HTML و پیوست‌ها استفاده کنید. گیرندگان و محتوا را پیش از رمزگذاری اعتبارسنجی کنید، header injection را رد کنید و محدودیت اندازه صریح تعیین کنید. پیش از فراخوانی Gmail، اقدام محصول را idempotent کنید: یک کلید پایدار رویداد برنامه، subject صندوق موردنظر و وضعیت تلاش ارسال را ذخیره کنید. پس از پاسخ موفق، شناسه پیام و شناسه رشته گفتگوی بازگشتی Gmail را همراه با آن رویداد ذخیره کنید. اگر کلاینت پس از ارسال درخواست دچار timeout شد، پیش از تلاش مجدد وضعیت صندوق را تطبیق دهید، چون ممکن است پیام قبلاً پذیرفته شده باشد. تلاش مجدد کورکورانه می‌تواند حتی وقتی پاسخ اصلی گم شده، ایمیل تکراری تولید کند. وقتی محتوا یا گیرندگان به تأیید نیاز دارند، از ایجاد پیش‌نویس به‌همراه بازبینی انسانی استفاده کنید.

تغییرات صندوق را با سوابق history همگام کنید

برای یکپارچه‌سازی صندوق در سمت سرور، یک watch در Gmail سیگنال‌های تغییر را از طریق Google Cloud Pub/Sub منتشر می‌کند. اعلان، تلنگری برای همگام‌سازی است، نه payload کامل ایمیل. history ID فعلی و زمان انقضای پاسخ watch را ذخیره کنید، اعلان‌ها را سریع تأیید (acknowledge) کنید و `users.history.list` را از آخرین history ID که با موفقیت ثبت شده فراخوانی کنید تا تغییرات پیام‌ها و برچسب‌ها را کشف کنید. فقط پیام‌هایی را که قابلیت موردنظر نیاز دارد دریافت کنید و پس از موفقیت نوشتن‌های محلی، checkpoint را جلو ببرید. اعلان‌ها ممکن است با تأخیر یا تکراری برسند، پس پردازش پیام و history را idempotent کنید. Gmail الزام می‌کند watch صندوق دست‌کم هر هفت روز یک بار تمدید شود و تمدید روزانه را توصیه می‌کند؛ تمدید را خیلی پیش از انقضا زمان‌بندی کنید و برای خطاها هشدار بگذارید. اگر history ID ذخیره‌شده خارج از بازه در دسترس Gmail باشد، API پاسخ HTTP 404 برمی‌گرداند. آن را یک مسیر بازیابی تعریف‌شده بدانید: یک همگام‌سازی کامل کنترل‌شده انجام دهید، checkpoint جدیدی بسازید و پردازش افزایشی را از سر بگیرید، به‌جای اینکه history ID نامعتبر را تا ابد دوباره امتحان کنید.

از یک گردش‌کار مرحله‌ای برای پیاده‌سازی و تأیید استفاده کنید

اول، مستند کنید که قابلیت ایمیل ارسال می‌کند، می‌خواند، تغییر می‌دهد یا پایش می‌کند و هر عملیات را به حداقل scope لازم در OAuth نگاشت کنید. دوم، پروژه‌های Google Cloud یا OAuth clientهای جداگانه برای توسعه و production با redirect URIهای دقیق و مالکان مشخص اعتبارنامه ایجاد کنید. سوم، مجوزدهی را با اعتبارسنجی state، دسترسی offline فقط در صورت نیاز، ذخیره‌سازی رمزنگاری‌شده token، ابطال token و بررسی دسترسی در سطح tenant پیاده‌سازی کنید. چهارم، با صندوق‌های کنترل‌شده آزمایش کنید: اتصال، تازه‌سازی access token منقضی‌شده، لغو رضایت، اتصال مجدد، یک بار ارسال، شبیه‌سازی timeout مبهم و تأیید جلوگیری از ارسال تکراری. پنجم، اگر تغییرات را دریافت می‌کنید، مجوزهای Pub/Sub را فراهم کنید، یک watch را آغاز کنید، history را به‌صورت افزایشی پردازش کنید، بازیابی از checkpoint قدیمی را عمداً اجرا کنید و تمدید watch را بررسی کنید. ششم، صف‌های کار به ازای هر کاربر، exponential backoff محدود، دسته‌بندی ساختاریافته خطاها و لاگ‌های ممیزی که به‌طور پیش‌فرض بدنه پیام‌ها و tokenها را حذف می‌کنند اضافه کنید. پیش از راه‌اندازی، هر تأیید و بازبینی امنیتی لازم Google را کامل کنید، اطلاعیه‌های دقیق استفاده از داده را منتشر کنید و چرخش اعتبارنامه‌ها و حذف داده‌های کاربر را تمرین کنید.

برای سهمیه‌ها، تلاش‌های مجدد و خطاهای جزئی برنامه‌ریزی کنید

Gmail مصرف API را با واحدهای سهمیه اندازه می‌گیرد، نه فقط تعداد درخواست. صفحه سهمیه Google، 1,200,000 واحد در دقیقه برای هر پروژه و 6,000 واحد در دقیقه برای هر کاربر در هر پروژه را فهرست می‌کند. `messages.send`، `drafts.send` و `watch` را هرکدام 100 واحد و محدودیت 500 گیرنده برای هر پیام فهرست می‌کند. محدودیت‌های جداگانه ارسال کاربر Gmail همچنان در کلاینت‌های API، وب و SMTP اعمال می‌شوند. Cloud console و مستندات کنونی را به‌جای hard-codeکردن محدودیت‌های منتشرشده در منطق کسب‌وکار، ورودی پیکربندی runtime بدانید. کار را برای هر صندوق ایمیل serialize یا به‌طور منصفانه صف‌بندی کنید، هم‌زمانی را سقف‌گذاری کنید و فقط پاسخ‌های گذرا را با exponential backoff دارای jitter و deadline محدود دوباره امتحان کنید. خطاهای مجوز، سیاست، گیرنده نامعتبر یا پیام بدشکل را گویی مشکل ظرفیت هستند دوباره امتحان نکنید. یک batch چندبخشی سربار اتصال را کاهش می‌دهد، اما هر فراخوانی داخلی همچنان سهمیه مصرف می‌کند و می‌تواند مستقل شکست بخورد.

پذیرش، تحویل و رسیدن به صندوق ورودی را جدا نگه دارید

فراخوانی موفق `messages.send` یعنی Gmail درخواست API مجاز را پذیرفته و یک منبع Message در Gmail برگردانده است. این ثابت نمی‌کند که سرور ایمیل همه گیرندگان پیام را پذیرفته است و نمی‌تواند مشخص کند سیستم گیرنده پیام را در کجا دسته‌بندی کرده است. تحویل به سرور گیرنده یعنی سیستم مقصد مسئولیت SMTP را پذیرفته است. رسیدن به صندوق ورودی نتیجه فیلترینگی است که بعداً رخ می‌دهد، مانند صندوق اصلی، promotions، قرنطینه یا اسپم. بنابراین API صندوق Gmail جایگزین جریان رویداد یک ارائه‌دهنده نیست، وقتی محصول برای ایمیل تراکنشی به داده‌های تحویل، برگشت ایمیل یا گزارش اسپم نیاز دارد. شناسه پیام Gmail را برای تطبیق نگه دارید، اما وضعیت قابل‌مشاهده برای کاربر را دقیقاً «ارسال‌شده» یا «پذیرفته‌شده توسط Gmail» توصیف کنید، مگر اینکه شواهد جداگانه‌ای تحویل را تأیید کند. احراز هویت، گیرندگان موردانتظار، کیفیت محتوا، رفتار ارسال و سیاست مقصد همگی بر پردازش در مراحل بعدی اثر می‌گذارند. پاسخ API نمی‌تواند پوشه نهایی صندوق گیرنده را تعیین یا تضمین کند.

بدانید چه زمانی API ایمیل تراکنشی برای کار دیگری مناسب است

وقتی محصول به دسترسی مجاز به صندوق ایمیل Gmail یک شخص یا سازمان نیاز دارد، از Gmail API استفاده کنید؛ از جمله برای threadها، برچسب‌ها، پیش‌نویس‌ها یا همگام‌سازی صندوق ایمیل. یک API ایمیل تراکنشی با معماری متفاوتی متناسب است: پیام‌های آغازشده توسط اپلیکیشن که از دامنه‌های تحت کنترل سازمان ارسال می‌شوند، بدون اختیار واگذارشده برای خواندن صندوق Gmail کاربر. وقتی مرزها صریح باشند، یک محصول می‌تواند از هر دو نوع سامانه استفاده کند؛ برای نمونه Gmail OAuth برای خواندن صندوق ایمیل متصل ایجنت پشتیبانی و یک ارائه‌دهنده تراکنشی جداگانه تأییدشده برای ارسال رسیدهای محصول. اعتبارنامه‌ها، رضایت، محل‌های ذخیره پیام، سیاست‌های تلاش مجدد و رکوردهای ممیزی را جدا نگه دارید تا اختیار صندوق ایمیل به ارسال سراسری اپلیکیشن نشت نکند و یک اعتبارنامه تراکنشی نتواند Gmail کاربر را بخواند.

پرسش‌های متداول

آیا حساب سرویس می‌تواند به هر صندوق Gmail دسترسی داشته باشد؟

خیر. حساب سرویس به‌طور خودکار به داده‌های کاربران Gmail دسترسی پیدا نمی‌کند. یک super administrator در Google Workspace باید domain-wide delegation را برای شناسه عددی client آن و scopeهای تأییدشده اعطا کند و پس از آن برنامه به‌صراحت هویت یک کاربر در آن سازمان را جعل (impersonate) می‌کند. برای حساب‌های Gmail شخصی، به‌جای آن از رضایت OAuth کاربر استفاده کنید.

یکپارچه‌سازی فقط‌ارسال با Gmail کدام scope در OAuth را باید درخواست کند؟

با ارزیابی `https://www.googleapis.com/auth/gmail.send` شروع کنید که اجازه ارسال از طرف کاربر را می‌دهد، بدون اینکه دسترسی عمومی خواندن صندوق ایمیل را اعطا کند. پیش از درخواست scope گسترده‌تر، مطمئن شوید هیچ الزام محصولی واقعاً به پیش‌نویس‌ها، خواندن پیام‌ها، برچسب‌ها یا تغییر نیاز ندارد و قواعد تأیید Google برای scopeهای حساس را در نظر بگیرید.

آیا ارسال موفق با Gmail API یعنی پیام تحویل شده است؟

خیر. فقط تأیید می‌کند که Gmail عملیات API مجاز را پذیرفته و یک رکورد پیام برگردانده است. پذیرش توسط سرور گیرنده و رسیدن به صندوق ورودی وضعیت‌های جداگانه‌ای در مراحل بعدی هستند. پیام را «تحویل‌شده» برچسب نزنید یا رسیدن به صندوق ورودی را وعده ندهید، مگر اینکه سیگنال قابل‌اعتماد دیگری این نتیجه را تأیید کند.

آیا اعلان‌های push در Gmail شامل کل پیام جدید هستند؟

خیر. اعلان Pub/Sub نشان می‌دهد که وضعیت صندوق تغییر کرده است و اطلاعاتی برای ادامه همگام‌سازی دارد. برنامه باید history در Gmail را از history ID ذخیره‌شده خود کوئری کند، داده‌های پیام لازم را دریافت کند، به‌صورت idempotent پردازش کند و سپس checkpoint خود را جلو ببرد.

watch صندوق Gmail هر چند وقت یک بار باید تمدید شود؟

Google الزام می‌کند `watch` دست‌کم هر هفت روز یک بار فراخوانی شود و تمدید روزانه را توصیه می‌کند. زمان انقضای بازگشتی را ذخیره کنید، پیش از آن تمدید کنید، خطاها را پایش کنید و یک job همگام‌سازی جایگزین نگه دارید تا یک تمدید ازدست‌رفته بی‌صدا شکاف داده نامحدودی ایجاد نکند.

تیم چه زمانی باید به‌جای Gmail API از API ایمیل تراکنشی استفاده کند؟

وقتی کار موردنظر ارسال ایمیل‌هایی است که برنامه از دامنه‌های تحت کنترل سازمان ارسال می‌کند و هیچ قابلیتی به دسترسی به صندوق Gmail یک شخص نیاز ندارد، از API ایمیل تراکنشی استفاده کنید. وقتی محصول مشخصاً به پیام‌ها، رشته‌های گفتگو، برچسب‌ها، پیش‌نویس‌ها، تنظیمات یا اختیار send-as در یک صندوق تفویض‌شده نیاز دارد، از Gmail API استفاده کنید.

منابع