46:def classify_error(msg: str) -> str: 57:def now(): 61:def _effective_invoice_date(invoice_date_str: str) -> date: 69:class SendTask(Task): 70: def on_failure(self, exc, task_id, args, kwargs, einfo): 74:@celery_app.task(bind=True, base=SendTask, name="app.tasks.send_tasks.send_one", 76:def send_one(self, campaign_id: int, campaign_recipient_id: int): 300:def _recipient_dict(r) -> dict: 306:def _plain_fallback(html: str) -> str: 313:def _unsubscribe_url(recipient, campaign) -> str: 320:def _sanitize_response(resp) -> str: 327:def _rate_window(key: str, interval: float): 349:def _reschedule(db, task, campaign_id, cr, job, delay: timedelta, reason: str): 360:def _handle_send_error(db, task, campaign, cr, job, account, attempt, error: str): 361: err_class = classify_error(error) 369: if err_class == "permanent": 372: attempt.error_class = err_class 392: attempt.error_class = err_class 408: attempt.error_class = err_class 416:def _push_dlq(campaign_id, cr_id, error): 425:def _publish_control(campaign_id, action, reason=""): 434:def _publish_progress(campaign_id):