Ok, I got there in the end. I’ve pasted the working demo flow below to help anyone who’s attempting something similar. @Silicon_Avatar got me halfway there, but the other issue was my misuse of setting variables for msg payloads. Here’s the working flow:
[{"id":"c7fa6c57.aa19e","type":"tab","label":"Dev","disabled":false,"info":""},{"id":"da5bca72.a8ae88","type":"inject","z":"c7fa6c57.aa19e","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":480,"wires":[["83cb3705.7adc08"]]},{"id":"736b0172.0c043","type":"function","z":"c7fa6c57.aa19e","name":"prep msg","func":"msg.filename = '/config/www/snapshots/snap.jpg';\nmsg.payload = { \n data: {\n type: 'photo',\n file: '/config/www/snap.jpg',\n chatId: YOUR_CHAT_ID,\n caption: 'Snapshot from camera.' }\n};\nreturn msg;","outputs":1,"noerr":0,"x":680,"y":480,"wires":[["bfcaabb3.6fca68","1b7063f9.8a2adc"]]},{"id":"83cb3705.7adc08","type":"api-call-service","z":"c7fa6c57.aa19e","name":"","server":"eb4f80de.bd93a","service_domain":"camera","service":"snapshot","data":"{\"entity_id\":\"camera.driveway_cctv\",\"filename\":\"/config/www/snap.jpg\"}","mergecontext":"","output_location":"","output_location_type":"none","x":420,"y":480,"wires":[["736b0172.0c043"]]},{"id":"bfcaabb3.6fca68","type":"api-call-service","z":"c7fa6c57.aa19e","name":"","server":"eb4f80de.bd93a","service_domain":"telegram_bot","service":"send_photo","data":"","mergecontext":"","output_location":"","output_location_type":"none","x":970,"y":480,"wires":[[]]},{"id":"1b7063f9.8a2adc","type":"debug","z":"c7fa6c57.aa19e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":900,"y":420,"wires":[]},{"id":"eb4f80de.bd93a","type":"server","z":"","name":"Home Assistant"}]
Note, the file name and write location are hardcoded at the moment. I’d like to change that, but each time I do, it’s like the call service method ignores the typical {{ msg.variable }} parsing and inserts it as a string literal, which isn’t ideal!