Back to Arky

Outgoing Email And Webhooks

Every email Arky sends and every webhook post, with its type and status

Email and webhook delivery evidence have separate records: OutgoingEmail and OutgoingWebhook. The business operation retains the outgoing record’s ID.

import type { OutgoingEmail, OutgoingWebhook } from "arky-sdk";

const emails = await sdk.notification.outgoingEmail.find({
  store_id: storeId,
  to: "customer@example.com",
  limit: 20,
});
const email = await sdk.notification.outgoingEmail.get({
  store_id: storeId,
  id: outgoingEmailId,
});
const posts = await sdk.notification.outgoingWebhook.find({
  store_id: storeId,
  webhook_id: webhookId,
});

OutgoingEmail has id, type, to, send_before, status, created_at and updated_at. Its tagged type is arky_sign_in, team_invite, customer_sign_in, event_email, receipt_resend, template_test, broadcast_email or support_reply. Event emails retain the event and template IDs; receipt resends retain the Order ID. The email status is waiting, sending, retrying, sent, not_sent or maybe_sent. Sending and retrying carry the attempt and recovery evidence; terminal statuses retain provider acceptance or the factual refusal/uncertainty.

OutgoingWebhook retains store_id, webhook_id, event_id, event_type, status and timestamps. Its terminal status is acknowledged or unacknowledged.

History

GET /v1/stores/{store_id}/outgoing-emails
SDK: sdk.notification.outgoingEmail.find()
GET /v1/stores/{store_id}/outgoing-emails/{id}
SDK: sdk.notification.outgoingEmail.get()
GET /v1/stores/{store_id}/outgoing-webhooks
SDK: sdk.notification.outgoingWebhook.find()
GET /v1/stores/{store_id}/outgoing-webhooks/{id}
SDK: sdk.notification.outgoingWebhook.get()

Email filters are to, type, order_id and broadcast_id, with limit and cursor. Webhook history has a webhook_id filter and pagination. The unresolved email request is immutable and encrypted. After the email finishes, its request content is removed; delivery evidence remains. Raw codes and Order tokens are never returned in this history.

Nothing Is Stopped

There is no stop. Nothing cancels an email or a post once it waits: not a second invite, not removing a team member, not a new code request, not an unsubscribe or a new suppression, not disabling a webhook. Instead, right before each try, Arky checks whether the message may still go, and a message that may not ends with its reason. There is no resend either: “resend receipt” makes a new outgoing email.

How Sending Works

Before each email try Arky checks, in order:

  1. the deadline: past send_before, the email ends not_sent with send_before_passed;
  2. the retry window and the tries left: at most 8 tries within 23 hours of the first;
  3. the store: a store being closed ends it not_sent with store_closing, a store whose Arky plan has no access with store_unavailable;
  4. the store’s email sending: while it is paused, the email waits without using a try;
  5. the sending address and the store’s email provider: an archived address, one without a sending side, or a store without a connected Resend account ends it not_sent with sender_cannot_send;
  6. the store’s suppressions: a broadcast stops at any active entry for the address, a sign-in code at none, every other store email at a block, a hard bounce or a complaint (recipient_suppressed).

Arky’s own mail skips the store checks. An email refused when it is queued, such as wording with no text in its language (no_text_in_language) or wording that can’t be built from the email’s data (render_failed: <reason>), starts not_sent and never reaches Resend.

Sending through Resend:

  • Store email goes through the store’s own Resend account and Arky’s own mail through Arky’s. Each account paces its own sends, so one store’s broadcast never slows another store’s mail.
  • The idempotency key is the email’s send key: its id, or for a broadcast email the broadcast and a hash of the address. An unclear answer (no answer, or a 408, 409, 424, 425 or 5xx) is tried again with the same key after 1 minute, 5 minutes, 30 minutes, 2, 6 and 12 hours. A 429 puts the email back without using a try. A refusal ends it not_sent.
  • After a try whose acceptance is uncertain, the email never ends not_sent: a Resend report settles it sent, or it ends maybe_sent.
  • No duplicates. An event email is unique per event, template and address; a broadcast email per broadcast and address; a webhook post per webhook and event. Handling the same event twice makes no second email.
  • Reports. Resend’s signed reports say whether an email was delivered, delayed, bounced or failed, and whether it was reported as spam. A hard bounce or a complaint adds the store’s suppression for that address.

Webhook posts read the webhook’s current URL, headers and secret on each try. A 2xx answer ends the post acknowledged. Otherwise it is tried again after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, and the eighth failure ends it unacknowledged. A disabled or deleted webhook, or a store being closed, ends a waiting post unacknowledged at its next try.

Cleanup. A finished outgoing email or webhook post is deleted 30 days after it finished; a support message keeps its own send history.

Warning

Never create a second email because one is maybe_sent. Arky can’t prove whether Resend accepted the first one.

Reading outgoing emails needs the store’s email permission. Reading webhook posts needs the store’s admin permission, like managing webhooks.