OPTIONS SHORT CIRCUIT IMPORT util IMPORT com FUNCTION apiclient(pemail) DEFINE data_apilayer RECORD email,did_you_mean,USER,domain,syntax_valid STRING, is_disposable,is_role_account,is_catch_all,is_deliverable,can_connect_smtp, is_inbox_full,is_disabled,mx_records,FREE,score BOOLEAN END RECORD DEFINE data RECORD data RECORD status , result, deprecation_notice string, score smallint, email string, regexp, gibberish, disposable, webmail, mx_records, smtp_server, smtp_check, accept_all, block BOOLEAN END RECORD, source DYNAMIC ARRAY OF STRING, meta RECORD param STRING END RECORD END RECORD DEFINE data_ipq RECORD MESSAGE STRING, success, valid, disposable BOOLEAN, smtp_score , overall_score DEC(10,2), first_name string, generic, common, dns_valid, honeypot BOOLEAN, deliverability STRING, frequent_complainer BOOLEAN, spam_trap_score STRING, catch_all, timed_out, suspect, recent_abuse BOOLEAN, fraud_score DEC(10,2), suggested_domain string, leaked BOOLEAN END RECORD DEFINE estado,purl,cmd,res, msg,s,pemail,tiposervice STRING, ret INT DEFINE dominio, detalle, api_url, api_key STRING, dns_estado SMALLINT, hunter_ok BOOLEAN # LA API PARA VERIFICAR ESTA OFFLINE 29/05/2025 { LET estado = 'VALIDO' LET ret = 200 RETURN estado,ret } # SE DEVUELVE SI EL EMAIL ESTA EN BLANCO LET pemail = pemail.trim() IF pemail IS NULL OR pemail.getLength() = 0 THEN LET ret = 400 LET estado = 'DEBES DIGITAR EL EMAIL POR FAVOR' RETURN estado,ret END IF DISPLAY "llego: ",pemail # VALIDACION PROPIA 14/10/2026: FORMATO + DOMINIO EN DNS (NO DEPENDE DE LA API) IF NOT email_formato_valido(pemail) THEN INSERT INTO envia_email_log (email,estado,fecha,programa_envio,us_crea,score) VALUES (pemail,'formato_invalido',getdate(),'mt101_local',suser_Sname(),0) LET ret = 400 LET estado = 'EL FORMATO DEL EMAIL NO ES VALIDO' RETURN estado,ret END IF LET dominio = pemail.subString(pemail.getIndexOf("@",1) + 1, pemail.getLength()) LET dns_estado = email_dominio_dns(dominio) DISPLAY "dominio: ",dominio," dns: ",dns_estado IF dns_estado = 0 THEN INSERT INTO envia_email_log (email,estado,fecha,programa_envio,us_crea,score) VALUES (pemail,'dominio_inexistente',getdate(),'mt101_dns',suser_Sname(),0) LET ret = 400 LET estado = 'EL DOMINIO DEL EMAIL NO EXISTE O NO RECIBE CORREO' RETURN estado,ret END IF # ELECCION DE SERVICIOS, POR EL MOMENTO ES IPQUALITY # URL Y API KEY SE LEEN DE LA TABLA APIS (YA NO VAN EN EL FUENTE) SELECT a.IDENTIFICACION, a.url, a.apikey INTO tiposervice, api_url, api_key FROM APIS a WHERE a.estado = 'ONLINE' IF STATUS = NOTFOUND THEN RETURN estado,ret END IF LET tiposervice = tiposervice.trim() LET api_url = api_url.trim() LET api_key = api_key.trim() # LET tiposervice = 'ipq1' IF tiposervice = 'HUNTER' THEN LET purl = api_url,"?email=",pemail CLIPPED,"&api_key=",api_key CALL ipquality_send_command(purl, NULL) RETURNING ret,res DISPLAY "response api: ",res," longitud: ",res.getLength() # HUNTER PUEDE NO RESPONDER (CAIDO, 202 EN PROCESO, 222/451 SMTP): # EN ESE CASO DECIDE LA VALIDACION DNS PROPIA LET hunter_ok = FALSE IF ret == 0 AND res.getLength() > 0 THEN TRY CALL util.JSON.parse(res,data) LET hunter_ok = TRUE CATCH LET hunter_ok = FALSE END TRY END IF IF NOT hunter_ok THEN INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score) VALUES (pemail,SFMT('hunter http=%1',ret),'hunter_sin_respuesta',getdate(),'mt101_dns',suser_Sname(),0) CALL email_resultado_dns(dns_estado) RETURNING estado,ret RETURN estado,ret END IF DISPLAY "DATA EMAIL: ",pemail CLIPPED, " score: ",data.data.score LET detalle = SFMT('mx=%1 smtp=%2 check=%3 regexp=%4 disp=%5 dns=%6', data.data.mx_records, data.data.smtp_server, data.data.smtp_check, data.data.regexp, data.data.disposable, dns_estado) INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score) VALUES (pemail,detalle,data.data.status,getdate(),'mt101_hunter',suser_Sname(),data.data.score) # DESECHABLES (MAILINATOR, ETC.) SE RECHAZAN SIEMPRE IF data.data.disposable THEN LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL' LET ret = 400 RETURN estado,ret END IF IF (data.data.status = 'valid' OR data.data.status = 'accept_all' OR data.data.status = 'unknown') AND data.data.score > 29 THEN LET estado = 'VALIDO' LET ret = 200 RETURN estado,ret END IF # HUNTER LO RECHAZA: SOLO SE RESPETA SI LLEGO AL SERVIDOR DE CORREO # Y ESTE RECHAZO EL BUZON (EL BUZON NO EXISTE) IF data.data.mx_records AND data.data.smtp_server AND NOT data.data.smtp_check THEN LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL' LET ret = 400 RETURN estado,ret END IF # HUNTER NO VE EL DOMINIO O NO PUDO CONECTAR (DOMINIOS .DO PEQUENOS): # SI EL DNS CONFIRMA QUE EL DOMINIO RECIBE CORREO, SE ACEPTA CALL email_resultado_dns(dns_estado) RETURNING estado,ret IF ret = 200 THEN INSERT INTO envia_email_log (email,display_name,estado,fecha,programa_envio,us_crea,score) VALUES (pemail,detalle,'valid_dns',getdate(),'mt101_dns',suser_Sname(),0) END IF RETURN estado,ret END IF IF tiposervice = 'IPQUALITY' THEN # EN APIS LA URL DE IPQUALITY YA TRAE LA KEY: .../email// LET purl = api_url,pemail CLIPPED CALL ipquality_send_command(purl, api_key) RETURNING ret,res IF ret ==0 THEN LET s = util.JSON.stringify(res) CALL util.JSON.parse(res,data_ipq) IF data_ipq.valid THEN LET estado = 'VALIDO' LET ret = 200 ELSE LET ret = 400 LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL' END IF END IF END IF IF tiposervice = 'APILAYER' THEN # DISPLAY " ESTOY EN APILAYER" LET purl= api_url,pemail CLIPPED CALL apilayer_send_command(purl, api_key) RETURNING ret, res # DISPLAY "res ",res," ",ret IF ret ==0 THEN LET s = util.JSON.stringify(res) CALL util.JSON.parse(res,data_apilayer) IF data_apilayer.can_connect_smtp THEN LET estado = 'VALIDO' LET ret = 200 ELSE LET ret = 400 LET estado = 'NO SE PUEDE ENTREGAR MENSAJE A ESTA DIRECCION EMAIL' END IF # DISPLAY "sintax ",data.syntax_valid," deliv ",data.is_deliverable ELSE IF ret = 429 THEN LET ret = 200 LET estado ='VALIDO POR EXESO DE REQUEST' ELSE LET ret = 400 LET estado = 'NO FUE POSIBLE LA CONEXION, ' END IF END IF END IF # LET ret = 200 DISPLAY "en return ",estado," ",ret RETURN estado,ret END FUNCTION # ## Communications library using com (http or https) # FUNCTION apilayer_send_command(url, papikey) DEFINE url, papikey STRING DEFINE result, tmp STRING DEFINE http_req com.HTTPRequest DEFINE http_resp com.HTTPResponse TRY UPDATE apis SET cuenta = cuenta + 1 WHERE estado = 'ONLINE' LET http_req = com.HTTPRequest.Create(url) CALL http_req.setHeader("apikey",papikey) CALL http_req.doRequest() LET http_resp=http_req.getResponse() IF http_resp.getStatusCode() != 200 THEN RETURN http_resp.getStatusCode(), http_resp.getStatusDescription() END IF LET tmp = http_resp.getStatusDescription() IF (tmp == "OK" || tmp == "no error" ) THEN RETURN -1, SFMT("HTTP request status description: %1 ",tmp) END IF LET result = http_resp.getTextResponse() CATCH RETURN -2, SFMT("HTTP request error: STATUS=%1 (%2)",STATUS,SQLCA.sqlerrd[2]) END TRY RETURN 0, result END FUNCTION FUNCTION ipquality_send_command(url, papikey) DEFINE url, papikey STRING DEFINE result, tmp STRING DEFINE http_req com.HTTPRequest DEFINE http_resp com.HTTPResponse # NO SE MUESTRA LA URL: LLEVA LA API KEY TRY LET http_req = com.HTTPRequest.Create(url) IF papikey IS NOT NULL THEN CALL http_req.setHeader("apikey",papikey) END IF CALL http_req.doRequest() LET http_resp=http_req.getResponse() IF http_resp.getStatusCode() != 200 THEN RETURN http_resp.getStatusCode(), http_resp.getStatusDescription() END IF LET tmp = http_resp.getStatusDescription() IF (tmp == "OK" || tmp == "no error" ) THEN RETURN -1, SFMT("HTTP request status description: %1 ",tmp) END IF LET result = http_resp.getTextResponse() CATCH RETURN -2, SFMT("HTTP request error: STATUS=%1 (%2)",STATUS,SQLCA.sqlerrd[2]) END TRY RETURN 0, result END FUNCTION # ## VALIDACION PROPIA DEL EMAIL (14/10/2026) ## Formato basico + el dominio debe existir y recibir correo (registro MX, o A ## como MX implicito segun RFC 5321). Se consulta el DNS por HTTPS (dns.google) ## porque BDL no tiene resolvedor DNS. # FUNCTION email_formato_valido(pemail) DEFINE pemail, usuario, dominio, etiqueta, c, permitidos STRING DEFINE i, arroba, inicio INT LET permitidos = "._%+-!#$&'*/=?^`{|}~" IF pemail IS NULL OR pemail.getLength() > 254 THEN RETURN FALSE END IF # UNA SOLA ARROBA, CON USUARIO Y DOMINIO LET arroba = pemail.getIndexOf("@",1) IF arroba <= 1 OR arroba = pemail.getLength() THEN RETURN FALSE END IF IF pemail.getIndexOf("@",arroba + 1) > 0 THEN RETURN FALSE END IF LET usuario = pemail.subString(1, arroba - 1) LET dominio = pemail.subString(arroba + 1, pemail.getLength()) # USUARIO: MAX 64, SIN PUNTO AL INICIO/FINAL NI DOBLE PUNTO IF usuario.getLength() > 64 THEN RETURN FALSE END IF IF usuario.getCharAt(1) = "." OR usuario.getCharAt(usuario.getLength()) = "." OR usuario.getIndexOf("..",1) > 0 THEN RETURN FALSE END IF FOR i = 1 TO usuario.getLength() LET c = usuario.getCharAt(i) IF NOT (c MATCHES "[a-zA-Z0-9]" OR permitidos.getIndexOf(c,1) > 0) THEN RETURN FALSE END IF END FOR # DOMINIO: AL MENOS UN PUNTO, ETIQUETAS DE LETRAS/NUMEROS/GUION, # SIN GUION AL INICIO/FINAL, Y EXTENSION (TLD) DE 2+ LETRAS IF dominio.getLength() > 253 OR dominio.getIndexOf(".",1) = 0 THEN RETURN FALSE END IF LET inicio = 1 FOR i = 1 TO dominio.getLength() + 1 IF i > dominio.getLength() OR dominio.getCharAt(i) = "." THEN LET etiqueta = dominio.subString(inicio, i - 1) IF etiqueta.getLength() = 0 OR etiqueta.getLength() > 63 THEN RETURN FALSE END IF IF etiqueta.getCharAt(1) = "-" OR etiqueta.getCharAt(etiqueta.getLength()) = "-" THEN RETURN FALSE END IF LET inicio = i + 1 ELSE LET c = dominio.getCharAt(i) IF NOT (c MATCHES "[a-zA-Z0-9]" OR c = "-") THEN RETURN FALSE END IF END IF END FOR # ETIQUETA FINAL = TLD IF etiqueta.getLength() < 2 THEN RETURN FALSE END IF FOR i = 1 TO etiqueta.getLength() IF NOT (etiqueta.getCharAt(i) MATCHES "[a-zA-Z]") THEN RETURN FALSE END IF END FOR RETURN TRUE END FUNCTION # 1 = EL DOMINIO RECIBE CORREO, 0 = NO EXISTE / SIN MX NI A, -1 = NO SE PUDO CONSULTAR FUNCTION email_dominio_dns(pdominio) DEFINE pdominio STRING DEFINE r SMALLINT LET r = email_dns_consulta(pdominio, "MX") IF r <> 0 THEN RETURN r END IF # SIN MX: EL REGISTRO A FUNCIONA COMO MX IMPLICITO RETURN email_dns_consulta(pdominio, "A") END FUNCTION FUNCTION email_dns_consulta(pdominio, ptipo) DEFINE pdominio, ptipo, res, dato STRING DEFINE http_req com.HTTPRequest DEFINE http_resp com.HTTPResponse DEFINE obj, rr util.JSONObject DEFINE answers util.JSONArray DEFINE i, dns_status, tipo_rr, tipo_buscado INT IF ptipo = "MX" THEN LET tipo_buscado = 15 ELSE LET tipo_buscado = 1 END IF TRY LET http_req = com.HTTPRequest.Create( SFMT("https://dns.google/resolve?name=%1&type=%2", pdominio.toLowerCase(), ptipo)) CALL http_req.setTimeOut(10) CALL http_req.setHeader("Accept","application/dns-json") CALL http_req.doRequest() LET http_resp = http_req.getResponse() IF http_resp.getStatusCode() != 200 THEN RETURN -1 END IF LET res = http_resp.getTextResponse() LET obj = util.JSONObject.parse(res) LET dns_status = obj.get("Status") CATCH DISPLAY "email_dns_consulta error: ",STATUS RETURN -1 END TRY # 3 = NXDOMAIN (EL DOMINIO NO EXISTE); OTRO <> 0 = FALLA DEL DNS IF dns_status = 3 THEN RETURN 0 END IF IF dns_status <> 0 THEN RETURN -1 END IF IF NOT obj.has("Answer") THEN RETURN 0 END IF IF obj.getType("Answer") <> "ARRAY" THEN RETURN 0 END IF LET answers = obj.get("Answer") FOR i = 1 TO answers.getLength() LET rr = answers.get(i) LET tipo_rr = rr.get("type") LET dato = rr.get("data") IF tipo_rr = tipo_buscado THEN # "0 ." ES UN NULL MX (RFC 7505): EL DOMINIO DECLARA QUE NO RECIBE CORREO IF tipo_buscado = 15 AND dato.trim() MATCHES "* ." THEN CONTINUE FOR END IF RETURN 1 END IF END FOR RETURN 0 END FUNCTION FUNCTION email_resultado_dns(dns_estado) DEFINE dns_estado SMALLINT CASE dns_estado WHEN 1 RETURN 'VALIDO', 200 WHEN 0 RETURN 'EL DOMINIO DEL EMAIL NO EXISTE O NO RECIBE CORREO', 400 OTHERWISE RETURN 'NO FUE POSIBLE VERIFICAR EL EMAIL, INTENTE DE NUEVO', 400 END CASE END FUNCTION