연결 응답에 connected가 true이고 sending_enabled와 delivery_verified가 false로 나와도 모순은 아니다. PC가 인증된 요청에 응답했지만, 예약 문자를 넘기는 허용은 꺼져 있고 실제 수신은 확인하지 않았다는 조합이다. 이때 ‘연결 성공’을 ‘문자 발송 성공’으로 바꾸면 남은 설정과 확인을 놓친다.

이 응답 뒤에는 발송용 단축어를 구성하는 작업이 이어졌다. 당시 Rust 소스와 작업 기록을 기준으로, 연결 뒤에 남은 단계와 각 상태가 뜻하는 범위를 짚어 본다. 실제 수신 여부는 그 기록에서도 미확인이다.

연결 여부와 발송 허용은 서로 다른 질문이다

연결 확인 요청은 인증 조건을 검사하고 최근 연결 시각을 남긴다. connected는 그 요청에 응답했다는 표시다. sending_enabled는 PC가 예약 문자를 아이폰에 넘기도록 허용했는지를 나타낸다. 허용을 끈 채 연결만 확인할 수 있다.

화면도 ‘연결 서버 실행 중’, 최근 인증된 연결 시각, 발송 전달 허용을 다른 항목으로 보여 준다. 최근 연결이 있는지 확인한 뒤 발송 허용을 선택할 수 있으며, 앱을 다시 열면 허용은 꺼진다. 연결이 살아 있다는 사실만으로 문자를 넘기기 시작하지 않도록 한 구성이다.

코드와 구현 살펴보기

연결 응답의 필드 예시 · 정적 설명용이며 새 연결·발송 영수증이 아님

{
  "connected": true,
  "sending_enabled": false,
  "delivery_verified": false
}

delivery_verified는 실제 수신 결과를 가져오는 값이 아니다

읽은 연결 처리 소스는 delivery_verified를 false로 반환한다. 단축어 결과를 기록하는 응답도 같은 값을 false로 둔다. 이 구현에는 그 값으로 통신사 접수나 상대방 수신을 확인하는 연결이 없다. 따라서 false를 보고 ‘문자가 도착하지 않았다’고 판정할 수도 없다. 확인하지 않은 범위를 표시한 것이다.

단축어가 메시지 보내기 동작 뒤에 action_completed를 알려 주면 PC에는 ‘단축어 동작 완료’가 남는다. PC가 관찰한 것은 그 결과 응답이다. 통신사 접수나 상대방 수신을 확인한 상태로 표시해서는 안 된다.

목록을 받는 요청이 이미 인계 기록을 만든다

예약 문자를 가져오는 claim은 단순 조회가 아니다. 발송 허용이 켜진 상태에서 예정된 항목을 골라, 아이폰에 넘기는 발송 기록을 저장하고 다음 예약을 갱신한 뒤 응답한다. 같은 예약과 예정 시각의 조합에는 고유 제약이 있고, 저장은 SQLite 트랜잭션 안에서 이뤄진다.

그래서 응답 화면을 못 봤다고 같은 인계가 없었다고 볼 수 없다. PC가 기록을 저장한 뒤 연결이 끊기면 아이폰이 내용을 못 받았더라도 그 항목을 자동으로 다시 넘기지 않는다. 이 선택은 중복 인계를 줄이는 대신 누락 가능성을 남긴다. 외부 문자 발송까지 정확히 한 번 실행된다는 보증으로 설명하지 않는다.

시간이 지난 이유에 따라 다음 상태가 달라진다

10분이라는 값은 같아도 기준 시각은 다르다. 아직 넘기지 않은 회차에는 예정 시각을 기준으로 발송 생략을 판단하는 조건이 있다. 이미 아이폰에 넘긴 항목은 인계 시각에서 10분을 넘겼는데도 확인 응답이 없으면 ‘결과 확인 필요’로 바꾼다. 반복 예약의 밀린 회차를 한꺼번에 보내는 구조도 아니다. 예정 시각과 인계 시각을 나눠 읽어야 생략과 미확인을 구분할 수 있다.

확인 응답이 없다는 이유로 항목을 새 발송 대기에 자동 복귀시키지 않는다. 늦게 도착한 단축어 결과는 원래 발송 ID와 연결해 기록할 수 있다. 같은 ID에 같은 결과를 다시 알려도 새 발송을 만들지 않고, 이미 기록한 결과를 다른 결과로 바꾸려는 요청은 거절한다. 그 결과가 실제 수신을 증명하는 것은 여전히 아니다.

단축어의 항목 반복과 예약 주기를 구분한다

설정 중 사용자는 ‘반복은 지금 안 해도 된다’고 했다가, 목록의 각 항목을 처리하는 반복이라는 설명을 듣고 그 단계를 이어갔다. 여기서 반복은 받아온 items를 순서대로 처리하는 단축어 동작이다. 매일·매주 같은 예약을 다시 만드는 주기와는 다른 뜻이다.

발송용 단축어를 설명할 때는 목록에서 문자 내용을 꺼내는 단계, 메시지 보내기 동작, 해당 항목의 결과를 PC에 알리는 단계를 구분한다. 이 채팅에서 확인한 것은 설정을 이어가는 과정이며 실제 수신 완료가 아니다. 항목 반복을 넣었다고 반복 예약이나 잠금 상태 발송까지 시험한 것으로 세지 않는다.

화면 표시가 늦으면 응답과 확인 시점을 함께 읽는다

작업 기록에는 PC의 백그라운드 화면이 예전 ‘연결 확인 전’ 표시를 유지한 일도 남아 있다. 이후 사용자가 연결 성공 응답을 제공했고, 화면을 갱신한 뒤 최근 인증된 연결 표시도 확인한 것으로 정정됐다. 화면 한 곳이 갱신되지 않았다는 사실을 인증 실패로 곧바로 해석했던 판단을 고친 기록이다.

다만 과거 한 번의 연결 확인으로 현재도 계속 연결돼 있다고 말할 수는 없다. 처음 검증 보고서는 아이폰 연결을 미확인으로 남겼고, 이후 채팅은 인증된 연결 성공을 추가했다. 별도 준비 빌드의 검사 통과와 실행 중 앱 반영도 나뉘어 있다. 시점과 버전을 붙여 읽어야 예전 미확인을 영구 실패로 남기거나 새 소스의 기능을 현재 앱에 있다고 설명하지 않게 된다.

시험 결과에도 어떤 단계인지 적는다

기존 시험 소스는 발송 허용이 꺼져 있을 때 인계 목록이 비는지, 같은 예정 항목을 다시 가져오지 않는지, 결과가 불확실해져도 재발송하지 않는지 확인한다. 모의 HTTP 시험은 메모리 저장소와 내부 라우터 요청으로 인증·인계·결과 기록을 확인한다. 실제 아이폰에서 메시지를 보내 상대가 받았다는 시험은 아니다.

이 글은 코드·기존 검사 보고서·사용자 응답을 대조한 설명이다. 앱 실행, 원래 데이터베이스와 연결 키 열기, 예약·수신자·문자 내용 조회, 연결 API 호출이나 문자 발송은 하지 않았다. 실제 수신과 잠금 상태 실행은 미확인이다. 아래처럼 상태마다 관찰한 대상과 다음 확인 항목을 붙여 두면 연결 성공 뒤에 무엇이 남았는지 알 수 있다.

  1. 연결 확인: 인증된 요청이 응답했는가. 언제 확인한 것인가.
  2. 발송 허용: 사용자가 예약 내용을 넘기도록 켰는가.
  3. 아이폰 인계: 어떤 예약·예정 시각·발송 ID를 저장했는가.
  4. 단축어 동작 완료: 원래 항목의 결과 응답을 기록했는가.
  5. 실제 수신: 별도로 확인했는가. 근거가 없으면 미확인으로 남기는가.