Guide
Messages from Qrona
With the add-on Byråstöd: Klientkommunikation, the firm's Qrona decides who gets what and when, and sends the finished data for one message at a time. What the message looks like is decided here, in a template designed once.
How it works
- The firm's workspace is connected to the firm's Qrona (Settings → Integrations → Connect Qrona), and the Qrona customer has Byråstöd. The add-on is then shown under Settings → Add-ons, and an administrator installs it.
- Qrona is told, with a system key for the workspace and the secret the receipts are signed with. The system key belongs to the organisation, not to a person, and has only the scopes
messages:sendandtemplates:render. It is revoked when the add-on is uninstalled or the connection is removed. - The templates are designed in Qrona Newsletter against Qrona's data contracts and published.
- Qrona previews with
POST /templates/{key}/renderand sends withPOST /messages. The receipts come back as signed webhooks.
curl -X POST 'https://newsletter.qrona.se/api/v1/messages' \
-H "Authorization: Bearer $QRONA_NEWSLETTER_SYSTEM_KEY" \
-H 'Content-Type: application/json' \
-d '{
"template": "byra.momsunderlag",
"to": { "email": "ekonomi@klienten.se", "name": "Eva Ekonom" },
"category": "service",
"data": { "klient": { "namn": "Exempel AB" }, "knapp": { "url": "https://byran.q.qrona.se/portal/underlag", "text": "Ladda upp" } },
"idempotency_key": "occ_20261023_0042",
"reference": "occ_20261023_0042",
"client_ref": "kl_7f3a"
}'Every field and error: Send message.
Templates and data contracts
A Qrona template has a key, the same as its data contract's in Qrona. The contract says which fields the message gets and has example data, shown in the editor and the preview. The values arrive finished – amounts formatted and dates written out – and Qrona Newsletter calculates nothing and looks nothing up.
- A field in a text, a button or the subject line:
{{data.klient.namn}}. The value is always shown as text: it can never become HTML or another field. - Lists are shown with the Table and List blocks – one row per item. A longer text field is shown with Text from data, in paragraphs.
- Show only if: a block can be shown only when a field is present or true, or only in newsletters.
- Publishing freezes the template as a new version. A published version never changes, and every message keeps its version. Publishing stops if the template uses a field the contract does not have.
| Type | Value |
|---|---|
text Text | A finished text (or a number). |
url Adress (URL) | An https address (mailto: and tel: work too). |
boolean Ja/nej | true or false – for Show only if. |
list Lista | A list of objects; items says which fields each row has. |
A field is required unless it says required: false. The data of a message is checked against the contract before anything is sent (422 with errors[] otherwise). An example:
{
"key": "byra.momsunderlag",
"version": 1,
"title": "Påminnelse om underlag till momsdeklarationen",
"fields": [
{
"path": "klient.namn",
"type": "text",
"label": "Klientens namn"
},
{
"path": "klient.orgnr",
"type": "text",
"label": "Organisationsnummer",
"required": false
},
{
"path": "period.etikett",
"type": "text",
"label": "Perioden"
},
{
"path": "period.intern_deadline",
"type": "text",
"label": "Byråns sista dag för underlag"
},
{
"path": "period.skv_deadline",
"type": "text",
"label": "Skatteverkets sista dag",
"required": false
},
{
"path": "konsult.namn",
"type": "text",
"label": "Konsultens namn"
},
{
"path": "konsult.epost",
"type": "text",
"label": "Konsultens e-post",
"required": false
},
{
"path": "konsult.telefon",
"type": "text",
"label": "Konsultens telefon",
"required": false
},
{
"path": "knapp.url",
"type": "url",
"label": "Länk till uppladdningen"
},
{
"path": "knapp.text",
"type": "text",
"label": "Knappens text"
},
{
"path": "saknas",
"type": "list",
"label": "Underlag som saknas",
"items": [
{
"key": "typ",
"type": "text",
"label": "Typ"
},
{
"key": "beskrivning",
"type": "text",
"label": "Beskrivning"
},
{
"key": "belopp",
"type": "text",
"label": "Belopp",
"required": false
}
]
},
{
"path": "bradskande",
"type": "boolean",
"label": "Brådskande",
"required": false
}
],
"example": {
"klient": {
"namn": "Exempel AB",
"orgnr": "556677-8899"
},
"period": {
"etikett": "september 2026",
"intern_deadline": "fredag 2 oktober",
"skv_deadline": "måndag 12 oktober"
},
"konsult": {
"namn": "Karin Konsult",
"epost": "karin@byran.se",
"telefon": "08-123 45 67"
},
"knapp": {
"url": "https://byran.q.qrona.se/portal/underlag",
"text": "Ladda upp underlaget"
},
"saknas": [
{
"typ": "Kvitton",
"beskrivning": "Inköp med företagskortet",
"belopp": "4 312 kr"
},
{
"typ": "Kontoutdrag",
"beskrivning": "Skattekontot, september"
}
],
"bradskande": false
}
}Service messages and newsletters
| category | Stopped by | Counts against |
|---|---|---|
service | A bounce, a spam complaint, erasure and a manual block – never an unsubscribe. Has no unsubscribe link. | The add-on's own monthly allowance, outside the monthly cap. |
marketing | All of the above, and unsubscribes, like the workspace's other mailings. Gets an unsubscribe link and List-Unsubscribe. | The organisation's monthly cap. |
A message that may not go is kept with the status suppressed and a reason, and the receipt message.suppressed is sent.
No duplicates
idempotency_key is unique per workspace forever, not just for a day. The same key again answers 200 with the first message and sends nothing new – so Qrona can repeat a call that got no answer. The same key with other content gives 409 idempotency_conflict. Use the mailing's id in Qrona.
Receipts
Every message gives receipts to Qrona's receiver /qrona-newsletter/messages/events, with the Qrona connection's credentials and signed like other webhooks (see Webhooks). Qrona recognises the message by reference and message_id.
| Event | When |
|---|---|
message.sent | The sending service accepted the message for delivery. |
message.delivered | The recipient's mail server accepted the message. |
message.bounced | The address does not accept email. It is blocked in the whole organisation. |
message.complained | The recipient marked the message as spam. The address is blocked in the whole organisation. |
message.failed | The message could not be delivered, and no more attempts are made. |
message.suppressed | The message was not sent: the address is blocked for the message's class, or the month's cap is reached. reason says why. |
POST /api/qrona-newsletter/messages/events HTTP/1.1
Content-Type: application/json
Qrona-Signature: t=1761208205,v1=…
Qrona-Event: message.delivered
Qrona-Delivery: 4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a{
"id": "2c9d4e1f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
"type": "message.delivered",
"created_at": "2026-10-23T08:30:05.000Z",
"workspace_id": "0b8f6c2a-1d3e-4f5a-9b7c-8d6e5f4a3b2c",
"data": {
"message_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"reference": "occ_20261023_0042",
"client_ref": "kl_7f3a",
"template": "byra.momsunderlag",
"template_version": 3,
"category": "service",
"email": "ekonomi@klienten.se",
"status": "delivered",
"reason": null,
"occurred_at": "2026-10-23T08:30:04.000Z"
}
}What is kept
- The data is used to make the message and is kept encrypted for the web version for 30 days. After that only its SHA-256 remains – Qrona is the source.
- The template version and the HTML's SHA-256 are always kept, and so are the receipts.
client_refis the firm's client, pseudonymously. The number of different clients that got a message in a calendar month is the basis of the invoice.