{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-10-22T10:38:37Z","object_id":{"id":3110,"is_a":["text","documentation","reference","module"],"name":"doc_external_10_mod_secure_message","title":"mod_secure_message","uri":"https:\/\/zotonic.com\/id\/3110"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-10-22T10:38:37Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2565,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_integrator","title":"Integrator","uri":"https:\/\/zotonic.com\/id\/2565"},"seq":3},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2594,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_email","title":"Email","uri":"https:\/\/zotonic.com\/id\/2594"},"seq":4},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":5},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2606,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_scheduled_and_background_work","title":"Scheduled and background work","uri":"https:\/\/zotonic.com\/id\/2606"},"seq":6},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2625,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_model","title":"Model","uri":"https:\/\/zotonic.com\/id\/2625"},"seq":7},{"created":"2026-10-22T10:38:37Z","object_id":{"id":2686,"is_a":["categorization","keyword","keyword_quality"],"name":"zotonic_topic_privacy","title":"Privacy","uri":"https:\/\/zotonic.com\/id\/2686"},"seq":8}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":3111,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3111"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_external_10_m_secure_message","page_url":{"en":"https:\/\/zotonic.com\/docs\/3111\/m_secure_message","x-default":"https:\/\/zotonic.com\/docs\/3111\/m_secure_message"},"preview_url":null,"resource":{"version":19,"pivot_location_lat":null,"title":"m_secure_message","is_authoritative":true,"body":"<p>Relay messages, resolve signed links and queue callbacks for\n<code>mod_secure_message<\/code>. Message records are returned as atom-keyed property\nlists; relay requests and callback payloads use binary-keyed maps.<\/p>\n<h2>Relay API<\/h2>\n<p>POST JSON to <code>\/api\/model\/secure_message\/post\/relay<\/code>, or call the model with\npath <code>[&lt;&lt;&quot;relay&quot;&gt;&gt;]<\/code> and <code>#{payload =&gt; Payload}<\/code>. This entry point requires\n<code>z_acl:is_allowed(use, mod_secure_message, Context)<\/code>; denial returns\n<code>{error, eacces}<\/code>. The API transport has its own authentication and topic ACLs.<\/p>\n<p>Required payload fields are <code>recipient<\/code>, <code>sender<\/code>, <code>subject<\/code> and <code>message<\/code>.\nOptional fields are <code>reply_url<\/code>, <code>status_url<\/code>, <code>message_id<\/code> and <code>language<\/code>.\n<code>message<\/code> is sanitized HTML. Invalid or omitted language values fall back to\nthe context language. <code>status_url<\/code> is stored but is not used for delivery:\nall message events go to <code>reply_url<\/code>, falling back to the configured\n<code>event_url<\/code> when the reply URL is undefined.<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_secure_message:m_post([&lt;&lt;&quot;relay&quot;&gt;&gt;], #{payload =&gt; #{\n    &lt;&lt;&quot;recipient&quot;&gt;&gt; =&gt; &lt;&lt;&quot;reader@example.com&quot;&gt;&gt;,\n    &lt;&lt;&quot;sender&quot;&gt;&gt; =&gt; &lt;&lt;&quot;Support&quot;&gt;&gt;,\n    &lt;&lt;&quot;subject&quot;&gt;&gt; =&gt; &lt;&lt;&quot;Your answer&quot;&gt;&gt;,\n    &lt;&lt;&quot;message&quot;&gt;&gt; =&gt; &lt;&lt;&quot;&lt;p&gt;Here is your answer.&lt;\/p&gt;&quot;&gt;&gt;,\n    &lt;&lt;&quot;message_id&quot;&gt;&gt; =&gt; &lt;&lt;&quot;case-123&quot;&gt;&gt;\n}}, Context).\n<\/code><\/pre>\n<p>The successful model result is\n<code>{ok, #{&lt;&lt;&quot;status&quot;&gt;&gt; =&gt; &lt;&lt;&quot;ok&quot;&gt;&gt;, &lt;&lt;&quot;result&quot;&gt;&gt; =&gt; #{&lt;&lt;&quot;message_id&quot;&gt;&gt; =&gt; Id}}}<\/code>. <code>Id<\/code> is the supplied message ID, or the generated\nmessage key when omitted. This confirms insertion; notification-email errors\nare recorded but do not turn the relay result into an error. Required fields\nare pattern-matched, not checked by a structured payload validator.<\/p>\n<h2>Signed-link lookup<\/h2>\n<p><code>m.secure_message.qlookup<\/code> reads the request&#39;s <code>key<\/code> and <code>signature<\/code>, checks\nthe signature and expiry, and returns the message property list with\n<code>view_count<\/code>. For example, within the signed-link page:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% if m.secure_message.qlookup as msg %}\n    {{ msg.subject|escape }}\n    {{ msg.message|sanitize_html }}\n{% endif %}\n<\/code><\/pre>\n<p>Lookup records a view, updates first-open information and queues a <code>read<\/code>\nevent. It is not a side-effect-free query. <code>m_get\/3<\/code> returns\n<code>{ok, {Message, Rest}}<\/code> or <code>{error, enoent}<\/code>; unsupported paths also return\n<code>enoent<\/code>. No logged-in user is required for valid signed-link access.<\/p>\n<h2>Direct Erlang calls<\/h2>\n<ul><li><code>insert(Props, Context)<\/code> accepts atom-keyed properties with the same required\nfields as relay, stores the message and sends the notification. It returns\n<code>{ok, GeneratedKey}<\/code> (a binary, not the numeric database ID) or an error.<\/li><li><code>get_url(Id, Context)<\/code> takes a numeric database ID and returns <code>{ok, Url}<\/code>\nfor an unexpired message, otherwise <code>{error, notfound}<\/code>.<\/li><li><code>fetch_by_key(Key, Signature, Context)<\/code> returns <code>{ok, Props}<\/code>,\n<code>{error, notfound}<\/code> for unknown\/expired keys, or <code>{error, signature}<\/code> for an\ninvalid signature. Unlike <code>qlookup<\/code>, it does not record a successful view.<\/li><li><code>delete(Key, Signature, Context)<\/code> checks signed-link access before deletion.\n<code>delete(Id, Context)<\/code> deletes by numeric ID without an ACL check.<\/li><li><code>send_link(Id, Context)<\/code> resends the notification for a numeric database ID.<\/li><\/ul>\n<p>Direct insertion, URL generation, resend and numeric-ID deletion are trusted\nserver APIs and do not repeat the relay entry point&#39;s ACL check.<\/p>\n<h2>Callback delivery<\/h2>\n<p><code>send_message_event\/3<\/code> accepts a decoded message property list or the binary\noutgoing email ID stored as <code>sent_message_id<\/code>. The latter is distinct from\nthe caller&#39;s <code>message_id<\/code> and the generated link key. It adds <code>message_id<\/code>\nand <code>recipient<\/code> to the event map and queues a JSON POST to <code>reply_url<\/code>.\n<code>send_generic_event\/2<\/code> queues a payload for the default <code>event_url<\/code>.<\/p>\n<p>Event names are <code>read<\/code>, <code>reply<\/code>, <code>sent<\/code>, <code>failed<\/code>, <code>bounced<\/code>, and <code>message<\/code>\nfor the standalone contact form. Replies add <code>subject<\/code>, <code>message<\/code> and\n<code>language<\/code>; delivery events may add <code>is_final<\/code> and <code>status<\/code>. Queued failures\nretry after 1800 seconds. A missing default URL returns <code>{error, url}<\/code>.\n<code>send_test_event\/1<\/code> immediately posts a <code>test<\/code> event to the default URL.\nAll HTTP calls use <code>z_fetch<\/code> as user 1; authentication comes from configured\nURL-fetch integrations, not module-specific OAuth consumer keys.<\/p>","slug":"m_secure_message","doc_module_config":[],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"8aed48baaa3f21106982bca6966c5145262baaad0b1e3233cde1a7c2cdb6255e","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"external_module_id":{"id":10,"is_a":[],"name":null,"title":null,"uri":"https:\/\/zotonic.com\/id\/10"},"git_url":"https:\/\/github.com\/zotonic\/zotonic_mod_secure_message.git","category_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"doc_source_path":".\/src\/models\/m_secure_message.erl","publication_start":"2026-10-22T10:38:38Z","github_url":"https:\/\/github.com\/zotonic\/zotonic_mod_secure_message.git","pivot_location_lng":null,"doc_source_kind":"external","name":"doc_external_10_m_secure_message","is_unfindable":false,"is_published":true,"pivot_geocode":null,"external_module_title":"Secure Message","created":"2026-10-22T10:38:37Z","uri":null,"doc_status":"current","is_dependent":false,"is_external_module":true,"publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"613b1e440b3dfa3c15596b16f0200bdab3ff4f21","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-22T10:38:38Z","title_slug":"m_secure_message"},"uri":"https:\/\/zotonic.com\/id\/3111","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3111"}},"status":"ok"}