m_secure_message
Relay messages, resolve signed links and queue callbacks for
mod_secure_message. Message records are returned as atom-keyed property
lists; relay requests and callback payloads use binary-keyed maps.
Relay API
POST JSON to /api/model/secure_message/post/relay, or call the model with
path [<<"relay">>] and #{payload => Payload}. This entry point requires
z_acl:is_allowed(use, mod_secure_message, Context); denial returns
{error, eacces}. The API transport has its own authentication and topic ACLs.
Required payload fields are recipient, sender, subject and message.
Optional fields are reply_url, status_url, message_id and language.
message is sanitized HTML. Invalid or omitted language values fall back to
the context language. status_url is stored but is not used for delivery:
all message events go to reply_url, falling back to the configured
event_url when the reply URL is undefined.
m_secure_message:m_post([<<"relay">>], #{payload => #{
<<"recipient">> => <<"reader@example.com">>,
<<"sender">> => <<"Support">>,
<<"subject">> => <<"Your answer">>,
<<"message">> => <<"<p>Here is your answer.</p>">>,
<<"message_id">> => <<"case-123">>
}}, Context).
The successful model result is
{ok, #{<<"status">> => <<"ok">>, <<"result">> => #{<<"message_id">> => Id}}}. Id is the supplied message ID, or the generated
message key when omitted. This confirms insertion; notification-email errors
are recorded but do not turn the relay result into an error. Required fields
are pattern-matched, not checked by a structured payload validator.
Signed-link lookup
m.secure_message.qlookup reads the request's key and signature, checks
the signature and expiry, and returns the message property list with
view_count. For example, within the signed-link page:
{% if m.secure_message.qlookup as msg %}
{{ msg.subject|escape }}
{{ msg.message|sanitize_html }}
{% endif %}
Lookup records a view, updates first-open information and queues a read
event. It is not a side-effect-free query. m_get/3 returns
{ok, {Message, Rest}} or {error, enoent}; unsupported paths also return
enoent. No logged-in user is required for valid signed-link access.
Direct Erlang calls
insert(Props, Context)accepts atom-keyed properties with the same required fields as relay, stores the message and sends the notification. It returns{ok, GeneratedKey}(a binary, not the numeric database ID) or an error.get_url(Id, Context)takes a numeric database ID and returns{ok, Url}for an unexpired message, otherwise{error, notfound}.fetch_by_key(Key, Signature, Context)returns{ok, Props},{error, notfound}for unknown/expired keys, or{error, signature}for an invalid signature. Unlikeqlookup, it does not record a successful view.delete(Key, Signature, Context)checks signed-link access before deletion.delete(Id, Context)deletes by numeric ID without an ACL check.send_link(Id, Context)resends the notification for a numeric database ID.
Direct insertion, URL generation, resend and numeric-ID deletion are trusted server APIs and do not repeat the relay entry point's ACL check.
Callback delivery
send_message_event/3 accepts a decoded message property list or the binary
outgoing email ID stored as sent_message_id. The latter is distinct from
the caller's message_id and the generated link key. It adds message_id
and recipient to the event map and queues a JSON POST to reply_url.
send_generic_event/2 queues a payload for the default event_url.
Event names are read, reply, sent, failed, bounced, and message
for the standalone contact form. Replies add subject, message and
language; delivery events may add is_final and status. Queued failures
retry after 1800 seconds. A missing default URL returns {error, url}.
send_test_event/1 immediately posts a test event to the default URL.
All HTTP calls use z_fetch as user 1; authentication comes from configured
URL-fetch integrations, not module-specific OAuth consumer keys.