رضایت، لغو اشتراک و سقف ارسال
چه چیزهایی جلوی یک ارسال را میگیرند و چرا این یک قابلیت است نه یک مانع.
این صفحه فهرست هر چیزی است که میتواند جلوی یک پیام را بگیرد. آن را بهعنوان قابلیت بخوانید، چون قابلیت است.
ابزار بازاریابی بدون حد و مرز، آدمی میسازد که سهشنبه پانزده اعلان میگیرد، اعلانها را خاموش میکند و برای همیشه از دسترس خارج میشود. کمپینی که این هفته موفق حس شد، مخاطب همهٔ کمپینهای بعدی را از بین برده است. هرچه پایینتر میآید برای این وجود دارد که آن اتفاق ممکن نباشد، از جمله همان بخشهایی که یک بازاریاب زیر فشار دوست دارد خاموششان کند.
هر چیزی که جلوی ارسال را میگیرد
هرکدام از اینها با کد دلیل خودش روی پیام ثبت میشود، تا گزارش بتواند جواب بدهد «سگمنت گفت شصت هزار نفر، چرا چهلویک هزار نفر گرفتند». همین سؤال رایجترین سؤالی است که به پلتفرمی مثل این میرسد، و همانی است که معمولا نمیتواند جوابش را بدهد.
| متوقفکننده | روی چه چیزی | کد دلیل |
|---|---|---|
| توقف اپراتوری | همهچیز، حتی هشدار امنیتی | suppressed |
| گروه کنترل کلی حساب | تبلیغاتی | global_holdout |
| کانالی که آدم خاموشش کرده | همهچیز جز critical | channel_opt_out |
| موضوعی که لغوش کرده | همهچیز جز critical | topic_opt_out |
| لغو اشتراک کلی | تبلیغاتی | unsubscribed |
| توقف موقت خودخواسته | تبلیغاتی | paused |
| انقضای خود پیام | همهچیز | expired |
| سقف تعداد | پیشفرض روی تبلیغاتی | frequency_cap |
| سقف نرخ حساب | هرچه خودش نام ببرد، هیچوقت critical | rate_limited |
| سقف کاهشیافته برای کسی که دیگر تعامل نمیکند | پیشفرض روی تبلیغاتی | fatigued |
| قاعدهٔ فاصلهٔ خود کمپین | همان کمپین | too_soon |
| ساعت سکوت | پیشفرض روی تبلیغاتی | quiet_hours |
| قاعدهٔ آخر هفتهٔ ایران | پیشفرض روی تبلیغاتی | weekend |
| گروه کنترل یک کمپین یا سناریو | همان کمپین یا سناریو | holdout |
| کلید قطع | هرچه آن کلید پوشش میدهد | switched_off |
| فراخوانی برگشت بعد از صف | همان پیام | recalled |
| باختن جا به پیام مهمتر | همان پیام | arbitrated |
| نبود دستگاه و شماره و گفتگو | همهچیز | not_reachable |
| متغیر قالب بدون مقدار و بدون جایگزین | همان پیام | missing_personalisation |
| رندری که هیچچیز تولید نکرد | همان پیام | empty_content |
| تمرین (حساب در حالت آزمایشی) | همهچیز | dry_run |
| خواندهنشدن جستجویی که تصمیم به آن وابسته است | همهچیز | *_unavailable |
سه دسته تعیین میکنند کدامیک از اینها اعمال میشوند.
| دسته | از چه رد میشود |
|---|---|
marketing | از هیچچیز |
transactional | همیشه از ساعت سکوت، و از یک سقف مگر آن سقف در applies خودش transactional را نام برده باشد |
critical | از همهٔ بالاییها، بهعلاوهٔ خاموشبودن کانال و لغو یک موضوع، و هیچ سیاستی نمیتواند عوضش کند |
دستهٔ حیاتی هیچوقت زیر هیچ سقفی نمیرود، هر چیزی که سیاست گفته باشد. این در خود کد اعمال میشود نه در اعتبارسنجی هنگام نوشتن، پس هیچ سیاست ذخیرهشدهای، به هر شکلی که ساخته شده باشد، نمیتواند خلافش را بیان کند. هشدار تقلب یا اطلاع تغییر رمز که پشت یک سقف تعداد بماند، همان شکستی است که کل این لایه برای جلوگیری از آن وجود دارد، و «مشتری خودش اینطور تنظیمش کرده بود» دفاعی نیست که بعدا کسی بپذیرد.
دسته از قالب میآید نه از فراخوان. همان فیلدی است که نمیگذارد تایید سفارش با ساعت سکوت تا نه صبح بماند و به همان اندازه نمیگذارد یک تبلیغ با برچسب تراکنشی از سقف تعداد رد شود.
ترتیب اجرای قاعدهها
ترتیب، جزئیات پیادهسازی نیست. تعیین میکند گزارش کدام دلیل را نشان بدهد، و یکی از قدمهایش تعیین میکند که عدد اثربخشی اصلا معنایی دارد یا نه.
| قدم | قاعده | روی چه چیزی |
|---|---|---|
| ۱ | توقف اپراتوری. هیچچیز زیر این خط نمیتواند پیام را دوباره مجاز کند. | همهچیز |
| ۲ | گروه کنترل کلی حساب | تبلیغاتی |
| ۳ | خاموشبودن کانال | همهچیز جز critical |
| ۴ | لغو موضوع، وقتی پیام موضوعی را نام برده باشد | همهچیز جز critical |
| ۵ | لغو اشتراک کلی | دستههایی که از محدودیتها رد نمیشوند، یعنی تبلیغاتی |
| ۶ | توقف موقت | تبلیغاتی |
| ۷ | انقضا: valid_until گذشته است | همهچیز |
| ۸ | حیاتی همینجا مجاز است، فقط مشروط به قدم آخر | critical |
| ۹ | سقف تعداد و سقف نرخ حساب | تبلیغاتی، بهعلاوهٔ هر دستهای که سقفی در applies نامش برده باشد |
| ۱۰ | ساعت سکوت و قاعدهٔ آخر هفته | فقط تبلیغاتی |
| ۱۱ | گروه کنترل | کمپین یا سناریوی مربوطه |
دو تا از این جایگاهها باربرند.
انقضا قبل از هر چیزی که به تعویق میاندازد بررسی میشود. ساعت سکوت پیام را به صبح میراند و قاعدهٔ آخر هفته آن را از جمعه رد میکند، پس «بازی امشب» شنبه میرسد مگر کسی بگوید کی دیگر مهم نیست. بررسیاش بعد از رضایت و قبل از بقیه هم یعنی پیام منقضی بهعنوان سقفخورده یا ساعتسکوتخورده گزارش نمیشود، که در آن حالت کسی میرفت قاعدهای را تنظیم کند که هیچ ربطی به ماجرا نداشت.
گروه کنترل آخر از همه بررسی میشود، بعد از سقف تعداد و ساعت سکوت. گروه کنترلی که هنوز آدمهایی را دارد که از سقف روزانهشان رد شدهاند، درحالیکه گروه هدف دقیق همانها را فیلتر کرده، خط پایه نیست. آدم خسته کمتر تبدیل میشود، پس نگه داشتنش در بازوی کنترل و بیرون گذاشتنش از بازوی هدف، هر کمپین را بهتر از آنچه بوده نشان میدهد، بهاندازهای که با حجم ارسال حساب تغییر میکند. اول رضایت و آخر گروه کنترل، همان چیزی است که هر دو بازو را بهمعنی «آدمهایی که این پیام را میگرفتند» درمیآورد.
رضایت: سه حالت، سه قول متفاوت
این سه عمدا همارز نیستند.
| حالت | قول | چه چیزی را میبندد |
|---|---|---|
suppressed | اپراتور این آدم را کنار گذاشته: بررسی تقلب، توقیف حقوقی | همهچیز، حتی هشدار امنیتی |
| خاموشبودن کانال | «با پیامک با من تماس نگیرید» | همهچیز روی آن کانال جز critical |
unsubscribed | «تبلیغات برایم نفرست» | فقط تبلیغاتی |
نسخهٔ قبلی این پلتفرم هر سه را مطلق میگرفت. امنتر به نظر میرسید و در همان یک جهتی که اهمیت دارد غلط بود: کسی که کد یکبارمصرف نمیگیرد نمیتواند وارد شود، و هیچچیز روی صفحه توضیح نمیداد چرا. خواندن لغو اشتراک تککلیکی بهمعنی «هیچ چیزی نفرست» یعنی هرکسی که تا حالا از یک خبرنامه لغو اشتراک کرده، از حساب خودش بیرون بماند.
خاموشبودن کانال برای همهچیز جز پیام حیاتی رعایت میشود، چون فرستنده کانالهای دیگری دارد و گیرنده همین یکی را بهطور مشخص نام برده است.
توقف موقت بهجای تعویق، دور میریزد. تعویق خوانش وسوسهانگیز است: توقف تاریخ پایان دارد، پس چرا تایمر را دوباره کوک نکنیم؟ چون تعویق سیروزه یک پیشنهاد سیروزهٔ کهنه تحویل میدهد، و چون همهٔ پیامهای متوقفشده در یک ماه شلوغ، روز برداشتهشدن توقف با هم میرسند. همان انفجار همان چیزی است که گیرنده از آن فرار میکرد. چیزی که بعد از توقف برمیگردد کمپین بعدی است، نه انباشت.
توقف در ساعت نه صبح به وقت محلی روز هدف برداشته میشود، نه در ساعتی که تنظیم شده. «سی روز متوقف کن» که ساعت ۲۳:۵۰ تنظیم شده و ۲۳:۵۰ برداشته شود، اولین پیام برگشتی را نیمهشب میگذارد، که دقیق همان تجربهای است که آدم برای فرار از آن سراغ توقف رفته بود.
دو ویژگی دیگر برای هرکسی که کد اطراف این را میخواند مهم است:
- رضایت روی پیام صفشده حمل نمیشود. پیامی که یک شب منتظر مانده، قبل از رفتن دوباره آن را میخواند. سریالایز کردنش یعنی تحویل جوابی که کسی هشت ساعت پیش پسش گرفته است.
- اجراکنندهٔ کمپین رضایت را صفحهبهصفحه از پیش میخواند، یک کوئری برای هر صفحه نه یکی برای هر گیرنده. شکست این پیشخوانی هیچ نگاشتی برنمیگرداند، که لایهٔ تصمیم آن را «پیشخوانی نشده» میخواند و به ازای هر گیرنده جستجو میکند. هیچوقت «مجاز است» خوانده نمیشود. باز شکستن آنجا تنها باگی در این ناحیه بود که واقعا به کسی که لغو کرده پیام میداد.
موضوعها: «بس کن» را قابل تقسیم کردن
پلتفرمی که فقط لغو اشتراک کلی دارد، هر شکایتی را، از «زیاد است» و «کانالش غلط است» تا «به این یکی علاقه ندارم»، به یک جواب برگشتناپذیر واحد تبدیل میکند، و فهرست فقط کوچکتر میشود.
موضوع یعنی یکی از انواع پیامی که میفرستید، با اسمی که خودتان گذاشتهاید، چون گیرنده میخواندش.
| فیلد | معنی |
|---|---|
key | نام ماشینی، ^[a-z0-9][a-z0-9_-]{0,63}$. بعد از ساخت قابل ویرایش نیست، چون کمپینها نگهش داشتهاند. |
name | چیزی که گیرنده میبیند. اجباری، حداکثر ۱۲۰ نویسه. |
description | حداکثر ۵۰۰ نویسه |
channels | رسانههایی که این موضوع اصلا رویشان میرود. خالی یعنی همه. |
default_on | true یعنی پیشفرض روشن، false یعنی پیشفرض خاموش. نبودنش یعنی true. |
position و archived | ترتیب و بازنشستگی |
اینکه یک ارسال مجاز است یا نه به این ترتیب جواب داده میشود:
- شناسهٔ موضوع صفر همیشه مشترک است. هر پیام تراکنشی، و هر کمپینی که قبل از وجود موضوعها نوشته شده. «بدون موضوع» هیچوقت نباید «مسدود» خوانده شود: آن خوانش، روزی که موضوعها منتشر شدند هر کد یکبارمصرف روی پلتفرم را ساکت میکرد.
- انتخاب صریح برنده است، در هر دو جهت. کسی که تیک جعبهای را زده که پیشفرضش خاموش بوده مشترک میماند، و تغییر بعدی پیشفرض نباید بیسروصدا بیرونش کند.
- موضوع بایگانیشده بسته میشکند. گیرنده دیگر آن را روی صفحهٔ تنظیماتش نمیبیند، پس هیچ راهی برای متوقف کردنش ندارد.
- موضوعی که روی این کانال فرستاده نمیشود بسته میشکند.
- در غیر این صورت
default_onخود موضوع جواب میدهد. - شناسهٔ موضوعی که به هیچچیز نمیرسد بسته میشکند: یا سطر حذفشده است یا کمپینی که به موضوع حساب دیگری اشاره میکند، و هیچکدام چیزی برای فرستادن نیست.
مدیریت موضوعها و تغییر اشتراک یک آدم دو مجوز جدا دارند، عمدا:
GET /v1/preferences/topics settings.read
POST /v1/preferences/topics settings.write
PATCH /v1/preferences/topics/{id} settings.write
DELETE /v1/preferences/topics/{id} settings.write (بایگانی، نه حذف)
POST /v1/preferences/topics/{id}/restore settings.write
POST /v1/preferences/topics/reorder settings.write
GET /v1/profiles/{user_id}/preferences profile.read
PUT /v1/profiles/{user_id}/preferences profile.write
مدیریت موضوعها یک صفحهٔ تنظیمات است و هیچچیز روی آن اسم یک آدم را نمیبرد. خواندن یا تغییر اشتراک یک نفر، پروندهٔ خود آن آدم است، و APIای که رویش مینویسد میتواند یک مشتری را ساکت کند یا کسی را که لغو کرده دوباره مشترک کند.
کلید تکراری 409 است؛ هر شکست اعتبارسنجی دیگری 400.
بدنهٔ PUT /v1/profiles/{user_id}/preferences:
{
"choices": [ { "topic_id": 4, "channel": "email", "subscribed": false } ],
"unsubscribe": true,
"pause_days": 30
}
هر فیلد اختیاری است و از false قابل تشخیص است، پس «گفته نشده» با «روی نادرست گذاشته شده» فرق دارد. بهروزرسانی جزئیای که چون فراخوان یک فیلد را جا انداخته بیسروصدا کسی را دوباره مشترک کند، بدترین باگ ممکن در این ناحیه بود. مقدار pause_days حداکثر ۳۶۵ است؛ پنل و مرکز تنظیمات ۳۰ و ۶۰ و ۹۰ را پیشنهاد میدهند.
وقتی یک اپراتور تنظیمات کسی را عوض میکند، منبع بهشکل agent:<who> ثبت میشود، هیچوقت preference_center. درخواست انطباقی که نتواند این دو را از هم تشخیص بدهد، درخواستی است که این پلتفرم نمیتواند جوابش را بدهد.
این مسیرها روی کنترلپلیناند، که در استقرار مرجع از اینترنت مسیردهی نشده است. روی میزبان مدیریتی اصلا هیچ مسیر رضایت یا تنظیماتی وجود ندارد. یکپارچهسازیای که کلید sk_seg_ دارد نمیتواند اشتراک کسی را بخواند یا تنظیم کند.
سقف تعداد
سقف یک شیء با شش دستگیره است، و همین یک ابزار بسته به تنظیمش به چند سؤال متفاوت جواب میدهد.
| فیلد | معنی |
|---|---|
channel | خالی یعنی همهٔ کانالها با هم شمرده شوند، که راه جلوگیری از این است که یک نفر از پنج تیم مختلف پنج بار پیام بگیرد |
window | پایینتر |
max | سقف |
applies | این سقف کدام دستهها را اداره میکند. خالی یعنی فقط تبلیغاتی. |
scope | شمارنده با چه کلیدی شمرده میشود |
budget | این سقف کدام مخزن را میشمارد. خالی یعنی هر ارسالی. |
بازهها:
| بازه | کی صفر میشود |
|---|---|
day | نیمهشب محلی |
week | شنبه، شروع هفتهٔ ایرانی |
month | اول ماه جلالی |
10m و 1h و 24h | لغزان |
interval:<duration> | بازهٔ لغزان با هر طولی |
بازهٔ هفته و ماه عمدا روی تقویم ایرانیاند. هفتهٔ ایزو از دید بازاریاب وسط هفته صفر میشود و میگذارد یک نفر داخل هفتهای که او میشناسد دو برابر سقف پیام بگیرد. سقف ماهانه روی تقویم اشتباه، عددی است که برای کسی که تنظیمش کرده هیچ معنایی ندارد.
نام بازهٔ ناشناخته بهجای رد شدن، روز تقویمی حساب میشود که تنگترین دورهٔ رایج است.
دامنهها:
scope | چه چیزی را میشمارد |
|---|---|
| خالی | یک نفر. پیشفرض، و معنی «سقف تعداد» بدون توضیح اضافه. |
tenant | کل حساب. جلوی یک سناریوی از کنترل خارجشده را میگیرد که مخاطب را اشباع میکند درحالیکه همهٔ سقفهای نفربهنفر رعایت شدهاند. |
campaign | کل خروجی یک کمپین. شعاع انفجار: اشتباهی که سر پنجاه هزار گرفته شود نه سر چهارصد هزار. |
recipient_campaign | سابقهٔ یک نفر با یک کمپین، که همان چیزی است که «بیشتر از هفتهای یک بار نه» میپرسد |
آخری از دامنهٔ نفربهنفر جداست و همین تمایز کل ماجراست: سقف نفربهنفر بودجهای است که همهٔ چیزهایی که میخواهند به کسی برسند در آن شریکاند، و خرج کردنش برای تنظیم فاصلهٔ یک کمپین تکرارشونده، همهٔ کمپینهای دیگر را تا آخر روز ساکت میکرد.
حساب تازه با ساعت سکوت ۲۳ تا ۹، منطقهٔ زمانی Asia/Tehran و سقفهای سه در روز و ده در هفته شروع میشود. این عمدا سختگیرانه است: کسی که هیچوقت این صفحه را باز نمیکند هم نباید بتواند مخاطبش را بسوزاند.
گرفتن جا اتمی است. «جا هست؟ اگر هست حالا مال من است» یک رفتوبرگشت است. شمردن و بعد افزودن، یک خواندن و یک نوشتن با فاصلهای بینشان است، و دو ورکر که به یک نفر میفرستند با هم توی همان فاصله میافتند: هر دو «دو از سه» میخوانند، هر دو میفرستند، و آن آدم چهار تا میگیرد. جا بلافاصله قبل از ترابری گرفته میشود و وقتی چیزی تحویل داده نشد آزاد میشود، پس قطعی یک ارائهدهنده بودجهٔ روزانهٔ کسی را خرج نمیکند.
خستگی سقف کسی را که دیگر تعامل نمیکند پایین میآورد و فقط میتواند پایین بیاورد. پیشفرضش این است: بعد از ده پیام پشت سر هم بیواکنش، سقف در نیم ضرب میشود، با کف یک. هیچوقت صفر، چون سکوت کاری است که لغو اشتراک برایش هست و سقفی که خودبهخود به صفر برسد آدم را بدون اینکه خودش خواسته باشد از دسترس خارج میکند. کسی که هیچوقت تعامل نکرده و تقریبا چیزی هم دریافت نکرده خسته نیست: قاعده برای او تا دو برابر آستانه صبر میکند.
بودجه میگوید یک سقف کدام مخزن را میشمارد و به سؤالی جواب میدهد که هر حسابی با بستهٔ پیامکی خریداریشده میپرسد: «این کمپین نباید از سهمیهٔ خبرنامه کم شود». استفاده از آن بهعنوان راه فرار ممکن نیست. ارسالی که بودجهای همراهش نیست زیر همهٔ سقفهای بیبودجه است، و کف حفاظتی هم ذاتا بیبودجه است، پس نام بردن یک بودجه فقط میتواند یک ارسال را زیر قاعدههای بیشتری ببرد.
مسیر PUT /v1/settings/governance همهٔ بازههای جدول بالا را میپذیرد بهجز interval:<duration>، که آن را اجراکنندهٔ کمپین از فاصلهٔ حداقلی خود کمپین میسازد و در سیاست نوشته نمیشود. سقفی با max کمتر از یک رد میشود: صفر یعنی «هیچ پیامی»، که کار خاموش کردن حکمرانی است، و پذیرفتنش یعنی دو راه برای بیان یک حالت داشته باشیم و هیچ راهی برای فهمیدن اینکه کدامش منظور بوده.
دو سقف وقتی یکی حساب میشوند که scope و channel و window و budget هر چهارتا یکی باشند. سقف روزانهٔ هر نفر و سقف روزانهٔ کل حساب دو شمارندهٔ متفاوتاند و هر دو با هم قابل تنظیماند.
سقف نرخ ارسال حساب
سقفی با scope: "tenant" و بازهٔ کوتاه، سقف نرخ است: نه اینکه هر نفر چند پیام میگیرد، بلکه کل حساب با چه سرعتی میفرستد.
{
"scope": "tenant",
"window": "1h",
"max": 10000,
"applies": ["marketing", "transactional"]
}
این سقف پیام را معوق میکند نه دور میاندازد، و تنها سقفی است که چنین میکند. بقیهٔ سقفها ارسال را دور میاندازند: کسی که سهم امروزش تمام شده نباید تا صبح نگه داشته شود، چون این یعنی شب را روی ساعت ۹ تلنبار کردهایم و دقیقاً همان برستی ساخته میشود که سقف جلویش را میگرفت. سقف نرخ سؤال برعکسی میپرسد، و پخش کردن ارسال روی زمان تمام کاری است که از آن خواسته شده. کمپین دو میلیونی در برابر سقف ده هزار در ساعت به همه میرسد، در حدود هشت روز، نه اینکه به ده هزار نفر اول برسد و بقیه را دور بیندازد.
پیام معوق با دلیل rate_limited ثبت میشود و لحظهٔ تلاش بعدی را همراه دارد: مرز بعدی برای بازهٔ تقویمی، و یک بازهٔ کامل از حالا برای بازهٔ لغزان. آزاد شدن روی حداکثر یکدهم بازه و حداکثر پنج دقیقه پخش میشود تا پیامهای یک بازه همه در یک لحظه از نو راه نیفتند. این جابهجایی از خود گیرنده مشتق میشود نه تصادفی، پس پیامی که چند بار تلاش شود روی همان لحظه مینشیند و با هر تلاش عقبتر نمیرود.
کمپینها خودشان را با سقف هماهنگ میکنند بهجای اینکه در آن بریزند: اجراکننده ظرفیت باقیماندهٔ حساب را یک بار برای هر صفحه از گیرندهها میخواند و وقتی صفحه جا نمیشود صبر میکند. به همین دلیل است که یک سقف، کمپین بزرگ را به میلیونها سطر در صف تبدیل نمیکند. این یک بهینهسازی است نه خود اعمال قاعده، که همچنان با همان برداشت اتمیک انجام میشود، پس دو کمپین که مستقل خودشان را کند میکنند باز هم نمیتوانند از سقف بگذرند.
applies پیشفرض خالی است، و خالی یعنی فقط تبلیغاتی. سقف نرخی که بدون آن نوشته شود، یک کمپین تبلیغاتی را میشمارد و هر ارسال تراکنشی را نشمرده رد میکند، در حالی که همان عددی را که گذاشتهاید نشان میدهد. طبقهها را صریح بنویسید. پنل همین کار را میکند.
هرچه applies بگوید، critical هیچوقت مشمول نمیشود. برای اینکه چه چیزی آنجا مینشیند طبقهها را ببینید، و توجه کنید که کد ورود بحرانی است نه تراکنشی، دقیقاً برای اینکه یک کمپین تبلیغاتی که ساعت حساب را پر کرده نتواند کدی را که کسی برای ورود منتظرش است نگه دارد.
ساعت سکوت
| فیلد | معنی |
|---|---|
start_hour و end_hour | به وقت محلی خود گیرنده، و وقتی شروع بعد از پایان باشد دور نیمهشب میپیچد |
skip_weekend | تبلیغات را در آخر هفتهٔ ایران هم ساکت میکند، که جمعه است |
policy | defer تا پایان بازه نگه میدارد، drop دور میریزد |
release_spread_minutes | پیامهای نگهداشتهشده در چه پهنایی آزاد شوند. صفر یعنی پانزده دقیقه. |
پیشفرض ۲۳ تا ۹ است، نه ۲۲ تا ۸ که بیشتر پلتفرمها میفرستند. تهران دیر میخوابد: مغازه ساعت ۲۲ باز است و روز کاری ساعت نه شروع میشود. ساکت کردن از ده شب یعنی دور انداختن پرواکنشترین ساعت شب.
مقدار drop فقط برای محتوایی درست است که ارزشش تا صبح از بین میرود، مثل حراج دوساعته. بقیه به تعویق میافتند، چون پیشنهاد ساعت نه صبح هم هنوز برقرار است.
پیامهای بهتعویقافتاده در یک پنجرهٔ پخش آزاد میشوند، پیشفرض پانزده دقیقه، با جابهجاییای که از هش پایدار شناسهٔ کاربر میآید. بدون آن، هر پیامی که بین ۲۳ و ۹ نگه داشته شده یک لحظهٔ آزادسازی یکسان دارد، پس کل ترافیک شب در همان یک ثانیه به درگاهها تحویل میشود: API خودتان جهشی میخورد که در طول روز هیچوقت نمیبیند، ارائهدهنده سقف نرخ میگذارد، و تلاشهای مجدد هم با هم میرسند. جابهجایی بهجای تصادفی از هش میآید تا پیامی که به ۹:۰۴ افتاده و دوباره تلاش شده، روی هر تلاش دیرتر و دیرتر نرود.
پنجرهٔ اختصاصی هر کانال پیشفرض را برای یک کانال کنار میزند، و پنجرهٔ کانالمحور بهجای ادغام، کامل برنده میشود. پیامک ساعت ۲۳:۳۰ گوشی را روی میز کنار تخت بیدار میکند؛ پیام درونبرنامهای داخل اپی منتظر میماند که کسی بازش نکرده، و ساکت کردنش یک ارسال را بدون هیچ فایدهای برای کسی خرج میکند. پنجرهٔ با پهنای صفر، یعنی شروع برابر پایان، راه گفتن «این کانال هیچوقت ساکت نیست» است. حذف کردن آن سطر بهجایش به پیشفرض برمیگردد، که برعکس منظور شماست.
سقف تعداد قبل از ساعت سکوت بررسی میشود، پس کسی که از قبل از سقفش رد شده بهجای تعویق دور ریخته میشود. تعویق او یعنی کل شب روی صبح تلنبار شود.
منطقهٔ زمانی هیچوقت به UTC برنمیگردد: اول منطقهٔ خود گیرنده، بعد پیشفرض حساب، بعد تهران. برگشتن به UTC یعنی ساعت سکوت برای کل کشور سهونیم ساعت جابهجا شود.
کفی که نمیتوانید خاموشش کنید
هرچه در سیاست هست مال شماست که تنظیمش کنید. این یکی نیست.
ساعت سکوت 22:00 تا 08:00
سقف 10 در روز، 30 در هفته
منطقه زمانی Asia/Tehran
این کف یک ثابت در کد است، نه سطری در حساب شما. کفی که کنار سیاستی ذخیره شود که قرار است محدودش کند، کفی است که یک نفر با همان صفحه، همان توکن API و همان اشتباه میتواند ویرایشش کند. بالا بردنش یک استقرار است، و نکته همین است: باید تصمیم ما باشد نه یک اهرم در داشبورد.
این عددها بازترین خوانش قابل دفاع از قاعدههای ایران بهعلاوهٔ چیزی است که مخاطب تحمل میکند، نه چیزی که ما توصیه میکنیم. مشتریای که سختگیرتر بخواهد سختگیرتر میگیرد؛ مشتریای که شلتر بخواهد همین را میگیرد.
بریدن به کف هنگام نوشتن انجام میشود نه هنگام خواندن، تا صفحه چیزی را نشان بدهد که واقعا اتفاق میافتد. کسی که سقف چهارصد تایپ کرده و بیسروصدا روی ده اعمال میشود، رابط کاربری به او دروغ گفته و هفتهها بعد از یک گزارش میفهمد. سقفی که کف لازم دارد و سیاست ندارد، اضافه میشود.
خاموش کردن حکمرانی کف را برنمیدارد. مقدار enabled: false قاعدههای خود شما را برمیدارد و کف را سر جایش میگذارد. این کلید یعنی «قاعدههای خودم را موقتا کنار بگذار» که خواستهٔ معقولی برای یک اپراتور است. چیزی که نمیتواند بهمعنی آن باشد این است که حالا میشود به آدم آن سر خط ساعت سه بامداد چهارصد بار پیام داد.
جدا از کف، پلتفرم پنجرهٔ قانونی ارسال تجاری را هم بهشکل داده نگه میدارد: سکوت از ۲۲ تا ۸، یعنی متمم ۸ تا ۲۲. این یک خوانش محافظهکارانه است. منبع مرجع برای پنجرهٔ تجاری چیزی نبود که بتوانیم قطعیاش کنیم، و راهنماهای منتشرشده ۸ تا ۲۲ یا ۸ تا ۲۳ میگویند؛ تنگتر همان است که آدم ترجیح میدهد در جهت امن اشتباه کند. بهشکل داده نگه داشته شده نه استنتاجشده از کف ساعت سکوت، تا وقتی کسی قطعیاش کرد، چیزی که عوض میشود یک عدد باشد نه شکل چیزی.
مرکز تنظیمات گیرنده
صفحهٔ خود گیرنده. میزبان ورود داده آن را بهشکل دو نقطهٔ HTTP ساده و یک صفحهٔ HTML خودبسنده سرو میکند. نه API جیسون، نه باندل، نه جاوااسکریپت.
آدمی که بازش میکند از یک پیوند داخل ایمیل آمده. اینجا حساب ندارد، نشست ندارد، و شاید اتصالی دارد که فایل دوم را نمیآورد. صفحهای که کل کارش این است که گزینهٔ راحتتری از دکمهٔ لغو اشتراک باشد، نمیتواند همان صفحهای باشد که گاهی بالا نمیآید. به همین دلیل فرم یک POST ساده است: هر تیکی که گیرنده میزند باید از مرورگر تعبیهشدهٔ یک کلاینت ایمیل جان سالم به در ببرد، و همانها اولین جایی هستند که میشکنند.
GET https://in.segmentic.net/e/p رندر صفحه
POST https://in.segmentic.net/e/p اعمال فرم
GET https://in.segmentic.net/e/u صفحهٔ تایید لغو اشتراک
POST https://in.segmentic.net/e/u خود لغو اشتراک تککلیکی
هر دو sg_mid (شناسهٔ پیام) و sg_t (توکن امضاشده) را حمل میکنند. هویت با راستیآزمایی توکن در برابر شناسهٔ پیام اثبات میشود. امضای بد و پیام ناشناخته دقیق یک جواب میگیرند، تا کسی نتواند بفهمد چه شناسههای پیامی وجود دارند.
فرم اینها را پیشنهاد میدهد: save و unsubscribe_all و resubscribe و pause (با pause_days) و resume. اقدام unsubscribe_all هر توقف موقتی را هم صریح پاک میکند، چون کسی که وسط یک توقف لغو اشتراک میکند حرف قویتر را زده و جا گذاشتن توقف زیر آن، بعدا بیسروصدا به «دوباره مشترک شد» منقضی میشد.
دو جزئیات که بهراحتی اشتباه میشوند و کل استدلال درستی همینهاست:
- تحلیلگر فرم روی جعبههایی که روی صفحه بودند میچرخد، نه روی فیلدهایی که رسیدهاند، و برای هر تیک یک فیلد پنهان همراه دارد. تیک زدهنشده اصلا در بدنهٔ POST نیست، پس خواندن فقط آنچه رسیده، «همه را برداشتم» را از «هیچی را عوض نکردم» غیرقابل تشخیص میکرد، و گیرنده ذخیره را میزد و میدید هیچ اتفاقی نمیافتد.
- بعد از ذخیره، وضعیت دوباره خوانده میشود نه بازتاب داده. این دو هرجا که چیزی رد شده باشد فرق دارند، مثل موضوع ناشناخته یا کانالی که موضوع رویش فرستاده نمیشود، و پس دادن همان فرم به گیرنده میگفت تغییری اعمال شده که نشده. تنها ارزش این صفحه این است که باورش میکنند.
موضوعی که فهرست کانال ندارد سه جعبه نشان میدهد، ایمیل و پیامک و اعلان، نه یکی به ازای هر کانال. کانالی که آدم خاموشش کرده غیرفعال و برچسبخورده نشان داده میشود، نه پنهان، چون کسی که تعجب میکند چرا خبرنامه قطع شده باید ببیند که خود ایمیل خاموش است.
اگر وضعیت تنظیمات اصلا خوانده نشود، صفحه باز هم یک نسخهٔ جایگزین رندر میکند که لغو اشتراک را پیشنهاد میدهد. فرستادن کسی که میخواست ایمیلش کمتر شود به صفحهٔ خطا، راهی است که یک مرکز تنظیمات به شکایت اسپم ختم میشود.
صفحهها با no-store و noindex, nofollow و Referrer-Policy: no-referrer سرو میشوند. این صفحه تنظیمات اشتراک یک نفر را دارد و با یک پیوند حامل باز میشود، پس سیاست ارجاعدهنده جلوی نشت آن پیوند، با امضا و همهچیزش، به هر میزبانی که یک تصویر یا یک کلیک به آن میرسد را میگیرد.
لغو اشتراک با GET، لغو نمیکند
مسیر GET /e/u صفحهای با یک دکمه رندر میکند. هیچکس را لغو نمیکند. این مهمترین بند این صفحه است.
اسکنرهای امنیتی بیشتر بانکها و خردهفروشیهای بزرگ ایران، هر پیوند هر پیام ورودی را قبل از اینکه گیرنده اصلا ببیندش دنبال میکنند. یک GET که آدمها را لغو کند، همان لحظهای که کمپین میرسد کل فهرست یک شرکت را بیسروصدا لغو اشتراک میکرد، و اولین نشانهاش برای مشتری، گزارش پوششی بود که فرو ریخته است.
مسیر POST /e/u همان مسیر تککلیکی RFC 8058 است. چیزی است که جیمیل و بقیه وقتی گیرنده دکمهٔ Unsubscribe کنار نام فرستنده را میزند صدا میزنند. آنجا عمدا هیچ مرحلهٔ تاییدی نیست: ارائهدهندگان صندوق پستی که حالا این هدر را میخواهند، صفحهٔ فرود را عدم انطباق حساب میکنند. پس دو فعل روی یک مسیر، دو محصول متفاوتاند و هرکدام در جای آن یکی غلط است.
لغو تککلیکی یک لغو کلی است نه کانالی، هرچند توکن، کانالی را که پیام از آن رفته نام میبرد. چیزی که آدمی که آن دکمه را میزند منظور دارد «بس کن» است. خواندنش بهمعنی «ایمیل نفرست، ولی پیامک و پیام بله ادامه پیدا کند» خوانشی است که هم از نظر حقوقی ضعیفتر است و هم برای گیرنده از نادیده گرفتنش قابل تشخیص نیست. کملغوکردن ریسک انطباق است و زیادلغوکردن ریسک بازاریابی، و فقط یکی از این دو قرارداد را تمام میکند.
آن POST حتی وقتی خرابی از سمت ماست 200 جواب میدهد. ارائهدهندهای که اینجا خطا ببیند ممکن است پیام را بهعنوان «لغو اشتراکش خراب است» نشان بدهد، و تلاشهای مجدد از زیرساخت یک ارائهدهنده چیزی نیست که ما بتوانیم شکلش بدهیم. بدنه میگوید چه شد؛ کد وضعیت میگوید ارائهدهنده نگران نباشد.
روی ایمیل، پیوندها را چیدمان اضافه میکند نه قالب شما:
- هدرهای
List-Unsubscribe: <...>وList-Unsubscribe-Post: List-Unsubscribe=One-Clickروی هر پیامی که نشانی لغو اشتراک دارد گذاشته میشوند. از سال ۲۰۲۴ جیمیل و یاهو روی ایمیل انبوه یکی از اینها را لازم دارند، و فرستندهای که ندارد تحویل کل دامنهاش افت میکند، نه فقط همان پیامی که جا انداخته. - پیوند دیدنی لغو اشتراک در پانویس میرود. پیوند مرکز تنظیمات کنارش میرود، هیچوقت بهجایش. پیشنهاد دادن فقط صفحهٔ تنظیمات به کسی که تصمیم گرفته برود، الگویی است که هر ارائهدهندهٔ صندوق پستی آن را تاریک حساب میکند، و جواب گیرنده به آن دکمهٔ اسپم است.
- پیوندهای خودتان برای رهگیری کلیک قبل از پیچیده شدن پوسته دور پیام بازنویسی میشوند، تا پیوند لغو اشتراکی که چیدمان بعدا اضافه میکند دستنخورده بماند. گیرنده باید همیشه یک کلیک با رفتن فاصله داشته باشد، بدون هیچچیز ما در مسیر.
جواب دادن به پیامک
پیامک تبلیغاتی ایرانی، دستور لغو لغو۱۱ را به انتهای متنش میگیرد. یک بار اضافه میشود: اگر متن از قبل لغو داشته باشد پانویس رد میشود. متن تراکنشی هیچوقت آن را نمیگیرد، چون خط خدماتی الگویی را حمل میکند که اپراتور تایید کرده و اضافه کردن به آن باعث میشود دیگر با الگو نخواند. بخش پیام تراکنشی را ببینید.
صورتحساب متن را با پانویس میشمارد. شمارش بخش در آهنگساز بدون آن گرفته میشود، پس یک پیامک تبلیغاتی میتواند یک بخشی اعلام و دوبخشی فاکتور شود.
جوابهای ورودی بعد از یکسانسازی، دقیق مقایسه میشوند و هیچوقت بهشکل زیررشته، پس پیامی که اتفاقا یک کلمهٔ توقف را وسط جمله دارد کسی را لغو نمیکند.
| معنی | کلمهها |
|---|---|
| لغو | لغو و لغو11 و لغو ۱۱ و لغو۱۱ و 11 و ۱۱ و 1 و ۱ و off و stop و cancel و unsubscribe و end و قطع و توقف و نمیخوام و نمیخوام |
| عضویت | شروع و عضویت و بله و start و on و yes و subscribe و 22 و ۲۲ |
پیامک یک فهرست لغو دوم دارد که با شمارهٔ تلفن کلید میخورد، جدا از رضایتی که با پرونده کلید میخورد، چون جواب از گوشیای میآید که شاید با هیچ پروندهای نخواند. فقط برای تبلیغاتی بررسی میشود، و این جستجو بسته میشکند: اگر خوانده نشود، پیامک تبلیغاتی نمیرود.
همهٔ کدهای دلیل
هرکدام روی پیام ذخیره میشوند و سمت سرور به یک جمله تبدیل میشوند، تا API و خروجی CSV و گزارش پوشش کمپین همگی یک حرف را بزنند.
| کد | معنی |
|---|---|
unsubscribed | لغو کلی. فقط تبلیغاتی. |
suppressed | کنارگذاشتن اپراتوری. همهچیز را میبندد. |
channel_opt_out | این رسانه خاموش است |
topic_opt_out | حکم مرکز تنظیمات. از لغو اشتراک جدا نگه داشته شده چون در هر گزارشی که بعدش میآید این دو واقعیت متضادند: یکی فهرستی است که هنوز کار میکند و دیگری فهرستی که نمیکند. |
paused | توقف خودخواسته با تاریخ پایان |
frequency_cap | از سقف رد شده |
rate_limited | از سقف نرخ حساب رد شده. تنها دلیل سقفی که بهجای دور انداختن، معوق میکند: پیام لحظهٔ تلاش بعدی را همراه دارد. جدا از frequency_cap نام دارد چون این دو خواننده را به دو عدد متفاوت میفرستند، و بالا بردن سقف هر نفر برای رفع سقف نرخ، چیزی را عوض میکند که هر گیرندهای دریافت میکند. |
too_soon | از قاعدهٔ فاصلهٔ خود کمپین رد شده. از سقف تعداد جداست چون این دو تصمیم دو آدم دربارهٔ دو چیز متفاوتاند. |
quiet_hours | داخل پنجرهٔ سکوت |
weekend | قاعدهٔ آخر هفتهٔ ایران |
holdout | گروه کنترل یک کمپین یا سناریو |
global_holdout | گروه کنترل کلی حساب |
not_reachable | نه دستگاهی، نه شمارهای، نه گفتگویی |
campaign_paused | کمپین متوقف شده |
recalled | صف شده و قبل از رفتن لغو شده. پیام موقع ساخته شدن درست بوده و تا وقتی میرسید غلط شده بود. |
switched_off | یک کلید قطع |
expired | valid_until گذشته. هیچچیز خراب نشده؛ گزارشی که آن را شکست نشان بدهد کسی را دنبالش میفرستاد. |
arbitrated | جایش را به پیام مهمتری باخته و کانال ارزانتری برای بازگشت نداشته |
fatigued | با سقف کاهشیافتهٔ کسی که دیگر تعامل نمیکند متوقف شده |
missing_personalisation | متغیر قالب نه مقدار داشت نه جایگزین |
empty_content | رندر اصلا چیزی تولید نکرد |
dry_run | تمرین: همهچیز اجرا شد جز ترابری |
dry_run_no_channel | تمرین روی کانالی که پیکربندی نشده |
policy_unavailable | سیاست خوانده نشد |
consent_unavailable | رضایت خوانده نشد |
topics_unavailable | موضوعها خوانده نشدند |
counters_unavailable | شمارندهها خوانده نشدند |
چهار تای آخر یادداشت خودشان را میخواهند. قبلا با چسباندن رشته ساخته میشدند، که مقداری بیرون از مجموعهٔ شناختهشده را وارد هر گزارشی میکرد که رندرش میکرد؛ داشبورد فارسی برای هرچه نشناسد به خود کد خام برمیگردد، پس یک بازاریاب وسط یک اختلال ردیس روی صفحهاش counters_unavailable خواند. حالا ثابتاند.
هرکدام به کدام جهت میشکند
بعضی چیزها باید موقع خرابی باز بمانند و بعضی بسته، و انتخاب اشتباه در هر جهت یک حادثه است.
| سازوکار | موقع خرابی | چرا |
|---|---|---|
| جستجوی سیاست و رضایت و موضوع و شمارنده | بسته | اگر ردیس خوابیده باشد نمیتوانیم بگوییم کسی امروز سه پیامش را گرفته یا نه، و حدس زدن «نگرفته» وسط قطعی، راهی است که یک مخاطب کامل دو بار کمپین میگیرد. نفرستادن برگشتپذیر است؛ فرستادن نیست. |
| کلید قطع کانال و حساب | باز | بسته شدنش یعنی یک قطعی ردیس کل نصب را ساکت میکند، از جمله کدهای ورودش. |
| جستجوی فراخوانی برگشت | باز | فراخوانی برگشت فقط چیزی را لغو میکند که از قبل مجاز بوده، پس قطعی بهجای تصمیم رضایت کسی، یک لغو را هزینه میکند. |
| فهرست لغو پیامکی با کلید شماره | بسته | این یک تصمیم رضایت واقعی است که بیرون از این سامانه گرفته شده. |
| حالت آزمایشی | بسته | حسابی که گفته نفرست و به آدم واقعی برسد، برگشتپذیر نیست. |
| سقف نرخ تراکنشی | باز | آن نقطهٔ ورود رسید سفارش و کد ورود حمل میکند. |
| بودجهٔ درخواست میزبان مدیریتی | بسته | گزارش و کوئری مخاطب حمل میکند. چکاوت کسی بهخاطر منتظر ماندن یک گزارش نمیشکند. |
| قاعدههای تایید کمپین | بسته |
کلیدهای قطع اول از همه بررسی میشوند، قبل از قالب و قبل از حکمرانی، و مشخصترین کلیدی که خاموش باشد همانی است که گزارش میشود. شش سطح دارند: اپراتور، حساب، کانال، مسیر، کمپین، و کمپینبهعلاوهٔ کانال.
فراخوانی برگشت فقط روی پیامی اعمال میشود که واقعا منتظر مانده باشد. ارسالی که در یک پاس تصمیم گرفته و تحویل داده شده پنجرهای برای برگشت خوردن ندارد، و پرداخت هزینهٔ آن جستجو روی تکتکشان یعنی گذاشتن یک رفتوبرگشت روی مسیر داغ برای جواب دادن به سؤالی که نمیتواند «بله» باشد. متوقف کردن یا لغو یک کمپین برای ارسالهایی که از قبل صف کرده یک برگشت صادر میکند، در حد توان و هیچوقت کشنده.
حالت آزمایشی بعد از همهچیز بررسی میشود، بعد از رضایت و سقف و ساعت سکوت و کلیدها و قالب و شخصیسازی، و فقط ترابری رد میشود. تمرین با is_test و دلیل dry_run ثبت میشود، پس هیچوقت نمیتواند در گزارش پوشش شمرده شود.
چیزی که وجود ندارد
- روی میزبان مدیریتی هیچ مسیر رضایت یا تنظیماتی نیست. یکپارچهسازی نمیتواند اشتراک کسی را بخواند یا تنظیم کند، نمیتواند کسی را لغو کند و نمیتواند موضوعها را فهرست کند. مسیرهای بالا روی کنترلپلیناند که استقرار مرجع منتشرش نمیکند.
- هیچچیز در کد منتشرشده
suppressedرا ست نمیکند. این حالت روی هر ارسال خوانده میشود و همهچیز حتی پیام حیاتی را میبندد، متد ذخیرهاش وجود دارد، و هیچ فراخوانی ندارد: نه مسیری، نه صفحهای در پنل، نه ورکری. امروز این یک نوشتن دستی در دیتابیس است. - هیچچیز جز برگشت دائمی یا شکایت اسپم با نشانی پنهان، کانالی را خاموش نمیکند. سرویس تحویلپذیری فقط روی گزارشی کار میکند که ارائهدهنده نشانی گیرنده را در آن پنهان کرده است: آدم را از شناسهٔ پیام داخل
message_logپیدا میکند و کانال ایمیل را خاموش میکند، هیچوقت کانال دیگری را. گزارشی که خود نشانی را میآورد از مسیر دیگری میرود: برگشت دائمی و شکایت اسپم و برگشت موقتی که تکرار شود، بهجایش یک سطرemail_suppressionsمینویسند، که خاموش کردن کانال نیست. APIای برایش نیست، و مرکز تنظیمات کانال خاموش را غیرفعال نشان میدهد بدون اینکه راهی برای روشن کردنش بدهد. آدمی که میخواهد پیامک خاموش باشد، مسیر خودخدمتی به خاموش کردن کانال ندارد. جوابلغوواقعا پیامک تبلیغاتی را متوقف میکند، ولی روی همان فهرست جدا با کلید شماره در بخش جواب دادن به پیامک مینشیند نه روی پرونده، و هیچ برگشت و شکایتی هم خاموشی کانال نمیسازد، چون آن نویسنده فقط ایمیل را دست میزند. - سقف با بازهٔ لغزان از API قابل تنظیم نیست. بخش سقف تعداد را ببینید.
- سقف تعداد بهتفکیک موضوع وجود ندارد. سقف با کانال یا حساب یا کمپین یا گیرنده کلید میخورد، هیچوقت با موضوع.
- ارسال تراکنشی
valid_untilندارد. قاعدهٔ انقضا در لایهٔ تصمیم هست وPOST /v1/messagesفیلدی برایش ندارد. بخش پیام تراکنشی را ببینید. - پیشنمایشی از اینکه یک تغییر سیاست چه چیزی را میبست وجود ندارد. بریدن به کف میگوید قاعدهها چه خواهند بود؛ هیچچیز نمیگوید از پیامهای ماه گذشته چند تا را متوقف میکردند.