Ghost에 외부 SMTP 연동하기

Ghost에 외부 SMTP 연동하기
Photo by Solen Feyissa / Unsplash

먼저 Ghost 운영 목적에 맞는 외부 SMTP 서비스에 가입한다.
대표적으로 Resend, Brevo (구 Sendinblue), SendGrid / Mailgun 등이 있는데, 나는 Resend를 사용해보기로 했다.

  1. Resend 웹사이트 https://resend.com/ 접속하여 계정을 생성한다.
  2. 계정 접속 후 좌측 [API keys] 메뉴에서 [Create API Key] 클릭.
  3. 키 이름을 클릭하고 권한을 Sending access로 설정한다.
  4. re_로 시작하는 API key를 복사한 뒤 개인 공간에 저장해놓는다.

Resend의 SMTP 접속 정보는 계정과 관계없이 모든 사용자에게 다음과 같이 고정되어 있다.

  • Host: smtp.resend.com
  • Port: 465 (SSL 사용 시) 또는 587 (TLS 사용 시)
  • Username: 무조건 고정 값인 resend 입력
  • Password: 앞서 발급받은 re_로 시작하는 API key

또한 메일이 스팸으로 빠지지 않고 정상 전송되려면 발신자 주소의 도메인이 Resend에 등록되어 있어야 한다.

  1. Resend 대시보드 좌측 메뉴에서 Domains 클릭.
  2. [Add Domain]을 눌러 보유하고 있는 도메인을 입력.
  3. 화면에 나오는 DNS 레코드(TXT, CNAME 등)를 본인 도메인 구매처의 DNS 설정에 그대로 등록.
  4. Resend에서 Verified 상태로 바뀌면, 해당 도메인을 활용해 noreply@yourdomain.com 같은 형식의 from 주소를 사용할 수 있다.

근데 내 Ghost 도메인은 ghost.jwheo.duckdns.org이다.
확인해보니 DuckDNS는 무료 동적 DNS (DDNS) 서비스이기 때문에 개인이 해당 도메인 전체(duckdns.org)의 DNS 레코드(TXT, CNAME 등)를 직접 소유하거나 제어할 수 없다고 한다.
따라서 Resend에서 duckdns.org를 통째로 도메인 인증하는 것은 불가능하다.

해결 방법은 아래 두 가지가 있다.

  1. Resend 기본 테스트 주소 사용하기
  2. 무료 커스텀 도메인 또는 개인 도메인 연결하기

방법 2는 메일 주소의 도메인이 깔끔하게 맞아 떨어진다는 장점이 있지만, 도메인을 새로 준비해야한다.
한편 방법 1은 추가 DNS 설정 및 도메인 등록 없이 API key만 넣으면 바로 메일 발송 기능이 동작하지만, 실제 메일을 보내는 용도로는 제약이 있을 수 있다.

나는 Ghost를 외부 타겟으로 운영할 계획이 없으므로, 방법 1로 진행해도 충분해보여 Resend 기본 테스트 주소를 사용해보기로 했다.
방법은 간단한데, Ghost 환경 변수 또는 설정 파일의 mail__from을 아래와 같이 설정하면 된다.

mail__from: "Ghost Blog <onboarding@resend.dev>"

다음으로 Dockge에서 Ghost의 compose.yaml을 아래와 같이 수정해준다.

services:
  ghost:
    image: ghost:5-alpine
    container_name: ghost
    restart: unless-stopped
    ports:
      - 2368:2368
    environment:
      - NODE_ENV=production
      - url=https://ghost.jwheo.duckdns.org
      - database__client=sqlite3
      - database__connection__filename=/var/lib/ghost/content/data/ghost.db
      - security__staffDeviceVerification=false
      # --- Resend SMTP 설정 추가 ---
      - mail__transport=SMTP
      - mail__options__host=smtp.resend.com
      - mail__options__port=465
      - mail__options__secure=true
      - mail__options__auth__user=resend
      - mail__options__auth__pass=여기에_발급받은_Resend_API_Key입력
      - mail__from=Ghost Blog <onboarding@resend.dev>
    volumes:
      - /home/jwheo/ghost/content:/var/lib/ghost/content
networks:
  nanobot-net:
    external: true

그대로 로그인 테스트를 해봤는데... 여전히 메일 전송에 문제가 있는 것 같다.
자세한 로그 내용은 아래와 같았다.

[2026-09-18 09:19:09] ERROR "POST /members/api/send-magic-link/" 500 2428ms

Failed to send email. Reason: Message failed: 550 The ghost.jwheo.duckdns.org domain is not verified. Please, add and verify your domain on https://resend.com/domains.

"Please see https://ghost.org/docs/config/#mail for instructions on configuring email."

Error ID:
    0123c190-b342-11f1-a3f0-d5194ec490df

Error Code: 
    EMESSAGE

방법 1은 resend.com에서 도메인을 인증받을 필요가 없지 않나?
AI에게 물어보니 아래와 같이 답변을 해줬다.

Ghost의 일부 기능(특히 Members/Subscriptions 기능인 매직 링크나 구독 확인 메일)은 사용자가 입력한 사이트 url([https://ghost.jwheo.duckdns.org](https://ghost.jwheo.duckdns.org))을 기반으로 발신자 주소(from)를 강제로 재조합하거나 오버라이드(Override)해버리는 성향이 있습니다.

즉, 설정 파일이나 환경 변수에 onboarding@resend.dev를 적어두었더라도, Ghost가 회원 가입/구독 메일을 쏠 때 내부적으로 "아, 이 사이트 도메인이 ghost.jwheo.duckdns.org니까 발신자도 이 도메인 기반이어야겠네?" 하고 가로채서 Resend로 던지기 때문에 Resend가 발끈하며 550 에러를 뿜어내는 것입니다.

결국 개인 유료/무료 도메인으로 변경하는 것 외에 다른 방법이 없는 것 같다.

Read more