I do this with ESPHome & a J115F21C12VDCS.9 relay (note only the NO side is rated for 40A resistive): https://i.imgur.com/MqqOkoY.png
Choose any of the temperature sensors here for air temperature sensing: https://esphome.io/
Configuration is so easy. For the sensor, just copy the config from here, for example: https://esphome.io/components/sensor/bme280. Add a gpio output (https://esphome.io/components/output/gpio) and a bang-bang climate controller (https://esphome.io/components/climate/bang_bang.html)
Here's the kicad footprint for that relay (Relay_SPDT_CIT-J115F2.kicad_mod) if you need it:
(module Relay_SPDT_CIT-J115F2 (layer F.Cu) (tedit 611825E5) (descr https://www.citrelay.com/Catalog%20Pages/RelayCatalog/J115F2.pdf) (tags "Relay CIT J115F2 SPDT") (fp_text reference REF** (at 12.1666 -15.2) (layer F.SilkS) (effects (font (size 1 1) (thickness 0.15))) ) (fp_text value Relay_SPDT_CIT-J115F2 (at 18.542 13.9 180) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) (fp_line (start -3.2 -14.1) (end 27.08 -14.1) (layer F.Fab) (width 0.1)) (fp_line (start -3.96 -13.34) (end -3.96 11.94) (layer F.Fab) (width 0.1)) (fp_line (start 27.84 -13.34) (end 27.84 11.94) (layer F.Fab) (width 0.1)) (fp_line (start -3.2 12.7) (end 27.08 12.7) (layer F.Fab) (width 0.1)) (fp_line (start -4.3 -14.3) (end 28.09 -14.3) (layer F.CrtYd) (width 0.05)) (fp_line (start -4.3 -14.3) (end -4.3 12.95) (layer F.CrtYd) (width 0.05)) (fp_line (start 28.09 -14.3) (end 28.09 12.95) (layer F.CrtYd) (width 0.05)) (fp_line (start -4.3 12.95) (end 28.09 12.95) (layer F.CrtYd) (width 0.05)) (fp_line (start 17.74 1.778) (end 19.304 1.778) (layer F.SilkS) (width 0.12)) (fp_line (start 23.876 1.778) (end 25.34 1.778) (layer F.SilkS) (width 0.12)) (fp_line (start 25.34 1.778) (end 25.34 6.604) (layer F.SilkS) (width 0.12)) (fp_line (start 21.59 -8.9) (end 17.526 -8.9) (layer F.SilkS) (width 0.12)) (fp_line (start 21.59 -2.54) (end 21.59 -8.9) (layer F.SilkS) (width 0.12)) (fp_line (start 21.59 -2.54) (end 22.8092 2.032) (layer F.SilkS) (width 0.12)) (fp_line (start 17.74 6.604) (end 17.74 1.778) (layer F.SilkS) (width 0.12)) (fp_line (start 22.86 1.778) (end 23.876 1.016) (layer F.SilkS) (width 0.12)) (fp_line (start 23.876 2.54) (end 22.86 1.778) (layer F.SilkS) (width 0.12)) (fp_line (start 20.32 1.778) (end 19.304 1.016) (layer F.SilkS) (width 0.12)) (fp_line (start 19.304 2.54) (end 20.32 1.778) (layer F.SilkS) (width 0.12)) (fp_line (start 23.876 2.54) (end 23.876 1.016) (layer F.SilkS) (width 0.12)) (fp_line (start 19.304 1.016) (end 19.304 2.54) (layer F.SilkS) (width 0.12)) (fp_circle (center 21.59 -2.54) (end 21.59 -2.413) (layer F.SilkS) (width 0.12)) (fp_line (start 4.064 -5.1054) (end 5.334 -5.1054) (layer F.SilkS) (width 0.12)) (fp_line (start 5.334 5.1054) (end 4.064 5.1054) (layer F.SilkS) (width 0.12)) (fp_line (start 5.334002 5.105397) (end 5.334002 1.777997) (layer F.SilkS) (width 0.12)) (fp_line (start 5.334 -1.778) (end 5.334 -5.1054) (layer F.SilkS) (width 0.12)) (fp_line (start -3.2 12.81) (end 27.08 12.81) (layer F.SilkS) (width 0.12)) (fp_line (start 27.95 -13.34) (end 27.95 11.94) (layer F.SilkS) (width 0.12)) (fp_line (start -4.07 -13.34) (end -4.07 11.94) (layer F.SilkS) (width 0.12)) (fp_line (start -3.2 -14.21) (end 27.08 -14.21) (layer F.SilkS) (width 0.12)) (fp_arc (start 27.08 -13.34) (end 27.84 -13.34) (angle -90) (layer F.Fab) (width 0.1)) (fp_arc (start 27.08 11.94) (end 27.08 12.7) (angle -90) (layer F.Fab) (width 0.1)) (fp_arc (start -3.2 11.94) (end -3.96 11.94) (angle -90) (layer F.Fab) (width 0.1)) (fp_arc (start -3.2 -13.34) (end -3.2 -14.1) (angle -90) (layer F.Fab) (width 0.1)) (fp_arc (start 5.334001 -0.381001) (end 5.842 -0.762001) (angle -286.3) (layer F.SilkS) (width 0.12)) (fp_arc (start 5.334002 0.380998) (end 5.842001 -0.000002) (angle -286.3) (layer F.SilkS) (width 0.12)) (fp_arc (start 5.334 -1.143) (end 5.334 -1.778) (angle -233.1) (layer F.SilkS) (width 0.12)) (fp_arc (start 5.334002 1.142997) (end 5.334002 1.777997) (angle 233.1) (layer F.SilkS) (width 0.12)) (fp_text user %R (at 12.3952 0) (layer F.Fab) (effects (font (size 1 1) (thickness 0.15))) ) (fp_arc (start -3.2 -13.34) (end -3.2 -14.21) (angle -90) (layer F.SilkS) (width 0.12)) (fp_arc (start -3.2 11.94) (end -4.07 11.94) (angle -90) (layer F.SilkS) (width 0.12)) (fp_arc (start 27.08 -13.34) (end 27.95 -13.34) (angle -90) (layer F.SilkS) (width 0.12)) (fp_arc (start 27.08 11.94) (end 27.08 12.81) (angle -90) (layer F.SilkS) (width 0.12)) (pad A2 thru_hole circle (at 2.54 5.1 180) (size 2 2) (drill 1.1) (layers *.Cu *.Mask)) (pad 14 thru_hole circle (at 17.74 8.9 180) (size 4 4) (drill 2.1) (layers *.Cu *.Mask)) (pad 12 thru_hole circle (at 25.34 8.9 180) (size 4 4) (drill 2.1) (layers *.Cu *.Mask)) (pad 11 thru_hole circle (at 15.2 -8.9 180) (size 4 4) (drill 2.1) (layers *.Cu *.Mask)) (pad A1 thru_hole roundrect (at 2.54 -5.1 180) (size 2 2) (drill 1.1) (layers *.Cu *.Mask) (roundrect_rratio 0.25)) (model ${KISYS3DMOD}/Relay_THT.3dshapes/Relay_SPDT_RAYEX-L90S.wrl (at (xyz 0 0 0)) (scale (xyz 1 1 1)) (rotate (xyz 0 0 0)) ) )
I do this with ESPHome & a J115F21C12VDCS.9 relay (note only the NO side is rated for 40A resistive): https://i.imgur.com/MqqOkoY.png
Choose any of the temperature sensors here for air temperature sensing: https://esphome.io/
Configuration is so easy. For the sensor, just copy the config from here, for example: https://esphome.io/components/sensor/bme280. Add a gpio output (https://esphome.io/components/output/gpio) and a bang-bang climate controller (https://esphome.io/components/climate/bang_bang.html)
Here's the kicad footprint for that relay (Relay_SPDT_CIT-J115F2.kicad_mod) if you need it: