chenhg5/cc-connect · error

dingtalk: send file failed: status=%d, body=%s

Error message

dingtalk: send file failed: status=%d, body=%s

What it means

DingTalk's oToMessages batchSend endpoint returned a non-200 HTTP status when sending a file message. The library logs the response body at debug level and surfaces the status code plus raw body in the error. This is an application-level rejection by the DingTalk API (auth, media, or payload problem), not a transport failure.

Source

Thrown at platform/dingtalk/dingtalk.go:1164

		"https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
		bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("dingtalk: create file request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-acs-dingtalk-access-token", token)

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("dingtalk: send file request: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	respBody, _ := io.ReadAll(resp.Body)
	slog.Debug("dingtalk: oToMessages file response", "status", resp.StatusCode, "body", string(respBody))

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("dingtalk: send file failed: status=%d, body=%s", resp.StatusCode, string(respBody))
	}

	slog.Info("dingtalk: file message sent", "media_id", mediaID, "name", name, "user", rc.senderStaffId)
	return nil
}

var _ core.FileSender = (*Platform)(nil)

// SendAudio uploads audio bytes to DingTalk and sends a voice message.
// Implements core.AudioSender interface.
// Uses DingTalk oToMessages API with msgKey: "sampleAudio" (voice messages).
// DingTalk voice messages only support ogg/amr formats (not mp3).
func (p *Platform) SendAudio(ctx context.Context, rctx any, audio []byte, format string) error {
	rc, ok := rctx.(replyContext)
	if !ok {
		return fmt.Errorf("dingtalk: SendAudio: invalid reply context type %T", rctx)
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the status and body in the error: 401/invalidAuthentication → refresh getAccessToken; 400 with mediaId error → re-upload the media
  2. Confirm the target user has an active session with the bot (oToMessages requires it)
  3. Check DingTalk rate limits and add throttling/retry on 429
  4. Enable debug logging to capture the full response body

Example fix

// before
token, err := p.getAccessToken()
if err != nil { return err }
// send with cached possibly-expired token
// after
token, err := p.getAccessToken() // ensure token is refreshed, cache with expiry
if err != nil { return err }
if strings.Contains(errBody, "invalidAuthentication") { p.invalidateToken(); retry once }
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: ensure the user has interacted with the bot and media was freshly uploaded
if time.Since(mediaUploadedAt) > 24*time.Hour { /* re-upload media before sending */ }

Try / catch

if err := p.SendFile(ctx, rc, data, name); err != nil {
    var se *dingtalk.StatusError
    if errors.As(err, &se) && se.StatusCode == 401 { refreshToken(); retry() }
    if se != nil && se.StatusCode == 429 { backoff(); retry() }
}

Prevention

When it happens

Trigger: Calling SendFile when the access token is expired/invalid (401), the mediaId is invalid or expired (media upload expired), the staffId/userId doesn't exist, the bot isn't in the target conversation, or a rate limit is hit (429).

Common situations: Access token expired between fetch and send, file uploaded with wrong media type, sending to a user who never interacted with the bot (DingTalk requires prior interaction for robot oToMessages), or DingTalk-side throttling.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/43be1ccb0233273c. Report an issue: GitHub.