custom_fields = {}
extra = {'company_phone': '555-0100', 'unsubscribe_link': 'http://localhost:8000/unsubscribe/test-token'}

    def substitute(self, text: str, recipient, generated: GeneratedValues,
                   custom_fields: Optional[dict] = None, extra: Optional[dict] = None) -> SubstitutionResult:
        recipient = recipient or {}
        custom_fields = custom_fields or {}
        extra = extra or {}
        # Accept ORM Recipient objects as well as plain dicts
        if not isinstance(recipient, dict):
            recipient = {c.name: getattr(recipient, c.name, None)
>                        for c in recipient.__table__.columns}
E           AttributeError: '_FakeRecipient' object has no attribute '__table__'
--
db = <sqlalchemy.orm.session.Session object at 0x7fbb16a4dbd0>

    def test_send_substitutes_variables_before_send(self, api, db):
        # covers the pre-send pipeline end-to-end up to the real network call;
        # the call itself fails honestly without Google credentials
        aid = _seed_account(db, user_id_of(api))
        r = api.post("/test-send/send", headers=H(api),
                     json={"account_id": aid, "to_email": "dest@example.com",
                           "subject": "Invoice %RandomInvoiceNumber%",
                           "body_html": "<p>Dear %FirstName%, total %RandomTotal%.</p>",
                           "use_random_variables": True})
>       assert r.status_code == 200, r.text
E       AssertionError: {"detail":"HTML rejected: Unsubstituted variables remain: ['FirstName', 'RandomTotal']"}
E       assert 400 == 200
E        +  where 400 = <Response [400 Bad Request]>.status_code
