Try this:
message: |
Some devices haven't been seen lately:
{% for state in states.sensor -%}
{%- if state.attributes.linkquality %}
{%- if "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(state.last_changed) > (12 * 60 * 60) ) -%}
. {{ relative_time(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) }} ago for {{ state.name | lower }} .
{%- endif -%}
{%- endif -%}
{%- endfor %}
This works because relative_time is expecting a datetime object, and the attributes are stored as strings. You apparently need to convert to a datetime, and the only way I see available to do that in the template is via strptime.
EDIT: Updated to use last_seen in both places and account for lqi=0.
message: |
Some devices haven't been seen lately:
{% for state in states.sensor -%}
{%- if not state_attr(state.entity_id, 'linkquality') == None and state.attributes.last_seen %}
{%- if "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) > (12 * 60 * 60) ) %}
{{ relative_time(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) }} ago for {{ state.name | lower }},
{%- endif -%}
{%- endif -%}
{%- endfor %}