docker-mediawiki icon indicating copy to clipboard operation
docker-mediawiki copied to clipboard

이메일 Bounce 관련 BounceHandler 확장기능 사용

Open lens0021 opened this issue 7 years ago • 5 comments

Extension:BounceHandler

특정 이메일이 매우 많이 전송 실패했다면 사용할 수 없는 이메일로 인식하여 위키에서 사용자에게 알림을 표시하고 더 이상 이메일을 보내지 않도록 처리하는 확장기능입니다.(기본값은 일주일간 10번 바운스되면 해지하는 것 같습니다)

MTA에서의 설정을 필요로 합니다. 내용은, 바운스가 일어날 때마다 https://femiwiki.com/api.php?action=bouncehandler으로 이메일 본문을 담은 POST 요청을 하여야 한다는 것입니다. AWS를 통해 이를 구현하기 위하여 다음 작업을 합니다.

  • SES에서 바운스 시 SNS(Amazon Simple Notification Service)에 알림을 보냄
  • SNS가 받은 알림을 SQS(Amazon Simple Queue Service)로 보냄
  • SQS가 아마존 Lambda를 트리거
  • Lambda에서 API 요청을 쏨

주의 SES가 다른 Region의 SNS로 메시지를 보낼 수 없는 듯하여 Region이 섞입니다

  • N. Virginia: SES, SNS
  • Tokyo: SQS, Lambda

요금 추산

  • SNS: SQS로 전송하는 비용은 부과되지 않음
  • SQS: 매월 첫 1000000건은 무료, 이후 요청당 0.0000004 USD
  • Lambda: 매월 첫 1000000건은 무료, 이후 요청당 0.0000002 USD
  • Bounce는 최근 6개월 기준 한 달에 평균 10.8건 있습니다.
  • +데이터 전송에도 요금 있음

TODO

  • [x] Extension:BounceHandler 설치
  • [ ] 새 SNS 토픽 생성(Topic Name: email_bounce)
  • [x] SES의 [email protected](Email Address)와 femiwiki.com(domain)에 email_bounce SNS 토픽을 bounces로 설정 ← 바운스된 이메일이 도메인 쪽으로만 전해지는 것 같습니다. 따라서 이메일은 안 켜도 될거 같은데 혹시 또 모르니까 둘다 켜놓습니다. --낙엽
  • [x] Include Original Headers enabled
  • [ ] 새 SQS 생성(Name: email_bounce), email_bounce 토픽에 Subscription으로 등록
  • [ ] 아마존 Lambda 함수 생성(Name: EmailBounceHandler)
    • [ ] 새 IAM Role EmailBounceHandler 생성하여 람다에 붙임.
    • [ ] IAM Role에 SQS 및 VPC 접근 정책 추가
    • [ ] SQS Trigger 생성
    • [ ] SQS Trigger 활성화
    • [ ] VPC 설정
    • [x] 미디어위키 메뉴얼을 참고하여 함수 작성
    • [x] VPC 서브넷에 따라 secret.php의 $wgBounceHandlerInternalIPs 변경
    • [ ] EC2 인스턴스의 private IP를 고정하거나 등 방법으로 API 호출을 계속 가능하게 처리
  • [ ] 테스트: 테스트를 위해선 인증된 메일을 가진 유저의 메일에서 bounce가 일어나야 합니다. 원할한 테스트를 위해서는 이런 상황을 인조적으로 만들 수 있어야 합니다.
  • ~~SES의 Email Feedback Forwarding 설정 끄기~~ 장기적으로 다른 쓸모가 있을 수 있어 취소
  • [ ] G메일의 반송 메일 삭제 필터 삭제

lens0021 avatar Oct 27 '18 05:10 lens0021

https://www.mediawiki.org/wiki/Extension:BounceHandler에 나온 설명을 보면, API에 email로 전달할 데이터는 bounce 되었다는 이메일 전문인 것 같은데 반해 SES에서 SNS로 쏘는 메시지는 대략 다음과 같은 json을 포함하고 있는 것으로 보입니다(일부 가림).

{
  "notificationType":"Bounce",
  "bounce":
  {
    "bounceType":"Permanent",
    "bounceSubType":"General",
    "bouncedRecipients":[
      {
        "emailAddress":"[email protected]",
        "action":"failed",
        "status":"5.4.4",
        "diagnosticCode":"smtp; 550 5.4.4 Invalid domain"
      }
    ],
    "timestamp":"2019-07-08T05:31:09.767Z",
    "feedbackId":...,
    "reportingMTA":...
  },
  "mail":{
    "timestamp":"2019-07-08T05:31:09.000Z",
    "source":...,
    "sourceArn":...,
    "sourceIp":"000.000.000.000",
    "sendingAccountId":...,
    "messageId":...,
    "destination":["[email protected]"]
  }
}

그래서 아마 https://github.com/wikimedia/mediawiki-extensions-BounceHandler/blob/2432d8099cbe673f37ce05aad0b756320f1cbe2e/includes/ProcessBounceWithRegex.php#L82-L99 코드에서 검사될 수 있도록 가상의 이메일 본문을 만들어서 API 리퀘스트를 날려야 할 것 같습니다...?

lens0021 avatar Jul 08 '19 07:07 lens0021

람다는 되는지 안 되는지 모르겠지만 일단 이렇게 짰습니다:

const http = require('http');
const querystring = require('querystring');
const validVerpRegex = /wiki-femiwiki-[a-z0-9]+-[a-z0-9]+-[a-zA-Z0-9+\/=][email protected]/;

exports.handler = async(event) => {
    // console.log('Received event:', JSON.stringify(event, null, 2));
    for (const { body } of event.Records) {
        console.log('Body: ', body);
        const message = JSON.parse(JSON.parse(body).Message);
        if (
            !message ||
            !message.mail ||
            !message.mail.commonHeaders ||
            !message.mail.commonHeaders.returnPath ||
            message.mail.commonHeaders.returnPath.match(validVerpRegex) === null
            ) {
            continue;
        }

        console.log(message);
        const pseudoEmail = `
Content-Type: multipart/report; report-type=delivery-status;
 boundary="FOO"
--FOO
Status: ${message.bounce.bouncedRecipients[0].status}
To: ${message.mail.source}
Date: ${message.mail.timestamp}
`;
        const payload = querystring.stringify({
            action: 'bouncehandler',
            email: pseudoEmail
        });
        const options = {
            hostname: '172.31.23.1',
            path: '/api.php',
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': Buffer.byteLength(payload)
            }
        };

        const req = http.request(options);

        req.on('error', (e) => {
            console.error(`problem with request: ${e.message}`);
        });

        req.write(payload);
        req.end();

    }
    return `Successfully processed ${event.Records.length} messages.`;
};

왜 되는지 안되는지 모르겠냐면 테스트를 위해서는 '인증에 성공한 이메일에 대하여 전송이 Bounce된' 상황이 필요하다는 걸 알았기 때문입니다. 즉 테스트를 할 만한 상황을 만들 수가 없었습니다. 그래서 현재는 SQS 트리거를 꺼둔 상태입니다.

lens0021 avatar Jul 13 '19 00:07 lens0021

삭제됨

lens0021 avatar Sep 23 '19 08:09 lens0021

  • https://temp-mail.org/ 으로 생성한 이메일로 인증을 받고 Delete 선택 후 다시 보내기 → 별 반응이 없습니다.
  • http://instance-email.com 으로 생성한 이메일로 인증을 받고 메일함 삭제 후 다시 보내기 → 별 반응이 없습니다.
  • 그나마 성공한 건 개발환경에서 $wgEmailAuthenticationfalse로 설정하고 [email protected](문서를 참고해주세요)로 메일을 보낸 건데요, 이 경우 람다가 실행되는 건 확인했지만 람다에서 개발환경의 API를 호출하게 할 수가 없어 끝까지 테스트를 하지 못했습니다. 정상적인 경우 호출된 API가 이메일을 보낸 목록과 반송된 목록을 비교하여 합당한 경우에만 정상적인 반송으로 기록합니다.

lens0021 avatar Sep 23 '19 13:09 lens0021

http://instance-email.com으로 생성 후 삭제한 이메일은 정상적으로 Bounce 되는걸 확인하였으며 필요한 경우 [[https://femiwiki.com/w/특수:이메일보내기/낙엽 봇|이 링크]]를 통해 이메일을 보내서 테스트해주세요.

https://github.com/femiwiki/femiwiki/issues/72 을 진행하면 영향이 있을 수 있을 것 같아 이 이슈는 잠시 Blocked로 이동하고 SQS 트리거도 꺼놓습니다.

lens0021 avatar Sep 26 '19 09:09 lens0021