Skip to content

Commit e9f1386

Browse files
Add 'message.attempt.recovered' operational webhook
This event is fired when a successful attempt is made after several attemtps have already failed. It already exists in the hosted svix.com product
1 parent 1cf8a7f commit e9f1386

File tree

5 files changed

+7968
-2
lines changed

5 files changed

+7968
-2
lines changed

server/openapi.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,69 @@
17471747
],
17481748
"type": "object"
17491749
},
1750+
"MessageAttemptRecoveredEvent": {
1751+
"description": "Sent on a successful dispatch after an earlier failure op webhook has already been sent.",
1752+
"properties": {
1753+
"data": {
1754+
"$ref": "#/components/schemas/MessageAttemptRecoveredEventData"
1755+
},
1756+
"type": {
1757+
"default": "message.attempt.recovered",
1758+
"enum": [
1759+
"message.attempt.recovered"
1760+
],
1761+
"type": "string"
1762+
}
1763+
},
1764+
"required": [
1765+
"data",
1766+
"type"
1767+
],
1768+
"type": "object"
1769+
},
1770+
"MessageAttemptRecoveredEventData": {
1771+
"description": "Sent when a message delivery has failed (all of the retry attempts have been exhausted) as a \"message.attempt.exhausted\" type or after it's failed four times as a \"message.attempt.failing\" event.",
1772+
"properties": {
1773+
"appId": {
1774+
"example": "app_1srOrx2ZWZBpBUvZwXKQmoEYga2",
1775+
"type": "string"
1776+
},
1777+
"appUid": {
1778+
"example": "unique-app-identifier",
1779+
"maxLength": 256,
1780+
"minLength": 1,
1781+
"nullable": true,
1782+
"pattern": "^[a-zA-Z0-9\\-_.]+$",
1783+
"type": "string"
1784+
},
1785+
"endpointId": {
1786+
"example": "ep_1srOrx2ZWZBpBUvZwXKQmoEYga2",
1787+
"type": "string"
1788+
},
1789+
"lastAttempt": {
1790+
"$ref": "#/components/schemas/MessageAttempetLast"
1791+
},
1792+
"msgEventId": {
1793+
"example": "unique-msg-identifier",
1794+
"maxLength": 256,
1795+
"minLength": 1,
1796+
"nullable": true,
1797+
"pattern": "^[a-zA-Z0-9\\-_.]+$",
1798+
"type": "string"
1799+
},
1800+
"msgId": {
1801+
"example": "msg_1srOrx2ZWZBpBUvZwXKQmoEYga2",
1802+
"type": "string"
1803+
}
1804+
},
1805+
"required": [
1806+
"appId",
1807+
"endpointId",
1808+
"lastAttempt",
1809+
"msgId"
1810+
],
1811+
"type": "object"
1812+
},
17501813
"MessageAttemptTriggerType": {
17511814
"description": "The reason an attempt was made:\n- Scheduled = 0\n- Manual = 1",
17521815
"enum": [
@@ -7742,6 +7805,30 @@
77427805
"Webhooks"
77437806
]
77447807
}
7808+
},
7809+
"MessageAttemptRecoveredEvent": {
7810+
"post": {
7811+
"description": "Sent on a successful dispatch after an earlier failure op webhook has already been sent.",
7812+
"operationId": "MessageAttemptRecoveredEvent",
7813+
"requestBody": {
7814+
"content": {
7815+
"application/json": {
7816+
"schema": {
7817+
"$ref": "#/components/schemas/MessageAttemptRecoveredEvent"
7818+
}
7819+
}
7820+
}
7821+
},
7822+
"responses": {
7823+
"2XX": {
7824+
"description": "Return any 2XX status to indicate that the data was received successfully"
7825+
}
7826+
},
7827+
"summary": "MessageAttemptRecoveredEvent",
7828+
"tags": [
7829+
"Webhooks"
7830+
]
7831+
}
77457832
}
77467833
}
77477834
}

server/svix-server/src/core/operational_webhooks.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ pub enum OperationalWebhook {
103103
MessageAttemptExhausted(MessageAttemptEvent),
104104
#[serde(rename = "message.attempt.failing")]
105105
MessageAttemptFailing(MessageAttemptEvent),
106+
#[serde(rename = "message.attempt.recovered")]
107+
MessageAttemptRecovered(MessageAttemptEvent),
106108
}
107109

108110
pub type OperationalWebhookSender = Arc<OperationalWebhookSenderInner>;

server/svix-server/src/openapi.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,13 @@ mod webhooks {
415415
common_: MessageAttemptEvent,
416416
}
417417

418+
#[derive(JsonSchema)]
419+
#[allow(unused)]
420+
struct MessageAttemptRecoveredEventData {
421+
#[serde(flatten)]
422+
common_: MessageAttemptEvent,
423+
}
424+
418425
webhook_event!(
419426
EndpointCreatedEvent,
420427
EndpointCreatedEventData,
@@ -451,6 +458,12 @@ mod webhooks {
451458
"message.attempt.failing",
452459
"Sent after a message has been failing for a few times.\nIt's sent on the fourth failure. It complements `message.attempt.exhausted` which is sent after the last failure."
453460
);
461+
webhook_event!(
462+
MessageAttemptRecoveredEvent,
463+
MessageAttemptRecoveredEventData,
464+
"message.attempt.recovered",
465+
"Sent on a successful dispatch after an earlier failure op webhook has already been sent."
466+
);
454467

455468
/// Generates documentation for operational webhooks in the Redoc `x-webhooks`
456469
/// format. For more info see https://redocly.com/docs/api-reference-docs/specification-extensions/x-webhooks/
@@ -462,6 +475,7 @@ mod webhooks {
462475
document_webhook::<EndpointUpdatedEvent>(),
463476
document_webhook::<MessageAttemptExhaustedEvent>(),
464477
document_webhook::<MessageAttemptFailingEvent>(),
478+
document_webhook::<MessageAttemptRecoveredEvent>(),
465479
])
466480
}
467481
}

server/svix-server/src/static/openapi.json

Lines changed: 7834 additions & 1 deletion
Large diffs are not rendered by default.

server/svix-server/src/worker.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,19 @@ async fn make_http_call(
407407

408408
#[tracing::instrument(skip_all, fields(response_code, msg_dest_id = msg_dest.id.0))]
409409
async fn handle_successful_dispatch(
410-
WorkerContext { cache, db, .. }: &WorkerContext<'_>,
410+
WorkerContext {
411+
cache,
412+
db,
413+
op_webhook_sender,
414+
..
415+
}: &WorkerContext<'_>,
411416
DispatchContext {
412417
org_id,
413418
endp,
414419
app_id,
420+
app_uid,
421+
msg_task,
422+
msg_uid,
415423
..
416424
}: DispatchContext<'_>,
417425
SuccessfulDispatch(mut attempt): SuccessfulDispatch,
@@ -432,6 +440,28 @@ async fn handle_successful_dispatch(
432440
tracing::Span::current().record("response_code", attempt.response_status_code);
433441
tracing::info!("Webhook success.");
434442

443+
if msg_task.attempt_count as usize >= OP_WEBHOOKS_SEND_FAILING_EVENT_AFTER {
444+
if let Err(e) = op_webhook_sender
445+
.send_operational_webhook(
446+
org_id,
447+
OperationalWebhook::MessageAttemptRecovered(MessageAttemptEvent {
448+
app_id: app_id.clone(),
449+
app_uid: app_uid.cloned(),
450+
endpoint_id: msg_task.endpoint_id.clone(),
451+
msg_id: msg_task.msg_id.clone(),
452+
msg_event_id: msg_uid.cloned(),
453+
last_attempt: attempt.into(),
454+
}),
455+
)
456+
.await
457+
{
458+
tracing::error!(
459+
"Failed sending MessageAttemptRecovered Operational Webhook: {}",
460+
e
461+
);
462+
}
463+
}
464+
435465
Ok(())
436466
}
437467

0 commit comments

Comments
 (0)