Skip to content

Scheduled send: daily-cap deferral vs. surfacing an unmet send_at #991

Description

@cyj-git-0825

Summary

#977 fixed the visibility symptom for deferred scheduled sends, but it exposed a deeper product question:

When a scheduled send cannot be submitted at its requested send_at because of the daily cap, should e2a silently send it later?

The current contract describes send_at as "not before," so silent deferral is technically consistent with the current behavior. The question is whether that is the right product behavior when timing is explicitly requested.

Current behavior

At fire time, the per-account daily send cap can prevent submission. Today the worker:

  1. releases the send claim (delivery_status returns to accepted);
  2. snoozes the job to the next UTC midnight; and
  3. leaves scheduled_at unchanged.

As a result, the message is silently deferred to a later window without the requester being told that the requested time was missed.

The visibility consequence of this was fixed in #977: an accepted message with a past scheduled_at now remains visible as Overdue. This issue is about the underlying send behavior, not the dashboard visibility.

Product question

There are three possible behaviors:

A — Best-effort / silent defer

Keep the current behavior: if the daily cap prevents sending, automatically retry in the next available window.

Pros

  • Preserves the current "not before / best-effort" contract.
  • No additional interaction required.

Cons

  • The actual send time can differ significantly from what was requested.
  • The requester has no way to know that the requested time was missed.

B — Require rescheduling

If the requested time cannot be honored, surface that explicitly and let the requester choose a new time or cancel.

Pros

  • Never changes an explicit requested time without consent.
  • Makes timing failures explicit.

Cons

  • Adds friction and requires a mechanism for the requester to respond.
  • For autonomous agents, this needs to be handled through an event/API rather than only through the dashboard.

C — Observable best-effort

Keep automatic deferral, but make it observable through a distinct state and/or event.

For example:

  • Dashboard shows the send as deferred/overdue with the reason.
  • A webhook/event notifies the requesting agent.
  • The operator or agent can then reschedule, cancel, or accept the deferred send.

This preserves best-effort delivery while removing the "silent" behavior.

Agent-first consideration

Because the requester is often an autonomous agent, the general mechanism should likely be state + event, rather than a dashboard-only interaction.

For example, an email.deferred or send.overdue event could allow:

  • a human operator to see and act on it in the dashboard;
  • an agent to decide programmatically via webhook/SDK/MCP.

This seems consistent with e2a's existing event-driven model.

Possible data-model implication

Depending on the decision above, we may also need to distinguish:

  • requested send time — what the requester specified (scheduled_at);
  • next attempt time — when the system will actually retry.

Today the latter exists only in the River job and is not exposed through the API.

If we choose an observable-defer model, this could eventually warrant a next_attempt_at field and/or a distinct deferred / needs_reschedule state.

Scope

In scope

  • Daily-cap behavior for scheduled outbound sends.
  • The send_at contract.
  • Any state/event needed to make deferral observable.
  • How the behavior is surfaced through dashboard, SDK/MCP, and webhook.

Out of scope

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions