Skip to content

🛠️ Improvement: crash-safe publication of project invitation tasks #9814

Description

@CaptainAni187

Split out of #9812, which restores the project invitation endpoint from a guaranteed HTTP 500. This is the reliability gap underneath it, left out of that PR deliberately to keep the fix reviewable.

ProjectInvitationsViewset.create persists the invitations and then publishes a task per row:

created_invitations = ProjectMemberInvite.objects.bulk_create(
    project_invitations, batch_size=10, ignore_conflicts=True
)
for invitation in created_invitations:
    project_invitation.delay(...)

The rows are committed before the first delay(). If the broker is unreachable partway through, earlier invitations exist and have been emailed, later ones exist and have not, and the endpoint returns 500. A client that retries then re-sends invitations that already went out, since bulk_create(ignore_conflicts=True) will not recreate the row but the loop will publish for it again.

Worth covering:

  • persist an outbox record in the same transaction as the invitation, so the intent to send is committed atomically with the row
  • publish from the outbox with retries, rather than inline in the request
  • key idempotency on ProjectMemberInvite id so a retry cannot email the same invitee twice

WorkspaceInvitationsViewset has the same shape, so whatever lands here probably wants to apply there too.

Happy to pick this up if it is wanted, though the outbox table and where the publisher runs are design calls worth agreeing first.

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions