        attachments, att_names = [], []
        if campaign.attach_mode == "pdf" and campaign.attach_pdf:
            att_html = ""
            if campaign.attach_pdf_template_id:
                att_tpl = db.get(EmailTemplate, campaign.attach_pdf_template_id)
                if att_tpl:
                    att_html = engine.substitute(att_tpl.body_html, rec, g, custom_fields=cf,
                                                 extra={"unsubscribe_link": unsub_url}).text
            elif campaign.attachment_id:
                att = db.get(Attachment, campaign.attachment_id)
                if att and att.kind == "html":
                    att_html = engine.substitute(att.html_attachment_body or "", rec, g,
                                                 custom_fields=cf,
                                                 extra={"unsubscribe_link": unsub_url}).text
            if att_html:
                pdf_bytes = message_builder.html_to_pdf(att_html)
                fname = message_builder.generate_attachment_filename(
                    var_cfg.get("attachment_kind", "invoice"), cr.generated_invoice_number)
                attachments.append({"filename": fname, "content": pdf_bytes,
                                    "mime_type": "application/pdf"})
                att_names.append(fname)
        if campaign.attach_mode in ("file", "image") and campaign.attachment_id:
            att = db.get(Attachment, campaign.attachment_id)
            if att:
                data = attachments_service.read_attachment_bytes(att)
                if data is not None:
                    attachments.append({"filename": att.filename, "content": data,
                                        "mime_type": att.mime_type})
                    att_names.append(att.filename)

        html_problems = message_builder.validate_html(result_html)
        if html_problems:
            log.warning("HTML validation warnings for %s: %s", cr.email, html_problems)

        mime = message_builder.build_mime_message(
            from_email=account.email,   # real authenticated account; never spoofed
            to_email=cr.email,
            subject=subject,
            html_body=result_html if campaign.message_type != "plain" else "",
            text_body=result_text,
            attachments=attachments,
            unsubscribe_url=unsub_url if campaign.unsubscribe_enabled else "",
            unsubscribe_mailto=f"{account.email}?subject=unsubscribe",
        )

        # ------------------------------------------------------------- send for real
        token = (db.execute(select(OAuthToken)
                            .where(OAuthToken.account_id == account.id))).scalar_one_or_none()
        if token is None:
            cr.status = "perm_failed"
            cr.last_error = "No OAuth token for account"
            campaign.failed_count += 1
            db.commit()
            return {"ok": False, "status": "perm_failed", "reason": "missing token"}

        cr.status = "processing"
        cr.attempt_count += 1
        if job:
            job.status = "processing"
            job.attempts = cr.attempt_count
        attempt = SendAttempt(
            send_job_id=job.id if job else 0,
            campaign_id=campaign.id,
            gmail_account_id=account.id,
            recipient_email=cr.email,
            subject=subject,
            template_name=template.name,
            attachment_names=att_names,
            method=account.sending_method,
            outcome="submitted",
            retry_no=cr.attempt_count - 1,
        )
        db.add(attempt)
        db.commit()

        try:
            client = build_gmail_client_from_account(account, token)
        except Exception as e:
            return _handle_send_error(db, self, campaign, cr, job, account, attempt, str(e))

        try:
            if client.method == "smtp":
                out = client.send_mime_smtp(mime)
            else:
                out = client.send_mime_api(mime)
        except googleapiclient.errors.HttpError as e:
            content = e.content.decode(errors="replace") if isinstance(e.content, bytes) else str(e.content)
            return _handle_send_error(db, self, campaign, cr, job, account, attempt,
                                      f"Gmail API error {e.resp.status}: {content[:300]}")
        except Exception as e:
            return _handle_send_error(db, self, campaign, cr, job, account, attempt, str(e))

        if not out.get("ok"):
            return _handle_send_error(db, self, campaign, cr, job, account, attempt,
                                      out.get("error", "unknown error"))

        # ------------------------------------------------------------- accepted
        msg_id = out.get("message_id", "")
        attempt.outcome = "accepted"
        attempt.message_id = msg_id
        attempt.thread_id = out.get("thread_id", "")
        attempt.provider_response = _sanitize_response(out.get("raw_response"))
        cr.status = "sent"
        cr.msg_id = msg_id
        cr.thread_id = out.get("thread_id", "")
        cr.sent_at = now()
        cr.last_error = ""
        campaign.sent_count += 1
        account.sends_today = (account.sends_today + 1) if (
            account.last_send_date and account.last_send_date.date() == today) else 1
        account.last_send_date = now()
        account.last_success_at = now()
        account.error_count = max(0, account.error_count - 1)
        if job:
            job.status = "sent"
        db.add(EmailEvent(campaign_id=campaign.id, campaign_recipient_id=cr.id,
                          gmail_account_id=account.id, event_type="sent",
                          message_id=msg_id, detail=f"accepted via {client.method}"))
        db.commit()
        _publish_progress(campaign.id)
        return {"ok": True, "status": "sent", "message_id": msg_id,
                "thread_id": out.get("thread_id")}


# ---------------------------------------------------------------------------
def _recipient_dict(r) -> dict:
