{"record":{"id":"d90823eb1c5fbf5b","repo":"Anankke/SSPanel-UIM","slug":"msg-response-getbody-getcontents-d90823","errorCode":null,"errorMessage":"$msg_response->getBody()->getContents()","messagePattern":"\\$msg_response->getBody\\(\\)->getContents\\(\\)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Services/Mail/Postmark.php","lineNumber":31,"sourceCode":"    {\n        $configs = Config::getClass('email');\n        $client = new Client();\n        $res = $client->post('https://api.postmarkapp.com/email', [\n            'headers' => [\n                'Content-Type' => 'application/json',\n                'X-Postmark-Server-Token' => $configs['postmark_key'],\n            ],\n            'json' => [\n                'From' => $configs['postmark_sender'],\n                'To' => $to,\n                'Subject' => $subject,\n                'HtmlBody' => $body,\n                'MessageStream' => $configs['postmark_stream'],\n            ],\n        ]);\n\n        if ($res->getStatusCode() !== 200) {\n            throw new Exception($msg_response->getBody()->getContents());\n        }\n    }\n}\n","sourceCodeStart":13,"sourceCodeEnd":35,"githubUrl":"https://github.com/Anankke/SSPanel-UIM/blob/d55a607191cfc51cdbc836fba85196ddef4df343/src/Services/Mail/Postmark.php#L13-L35","documentation":"This line is broken twice: the Postmark response lives in $res, but the throw reads $msg_response, a variable that does not exist in this scope, and the class never imports Exception (no \"use Exception;\"), so \"new Exception\" resolves to the non-existent App\\Services\\Mail\\Exception. The intended trigger is a non-200 from POST https://api.postmarkapp.com/email (401 bad X-Postmark-Server-Token, 422 unprocessable recipient/From, 5xx), but the moment that branch runs PHP first warns about the undefined variable and then fatals with \"Call to a member function getBody() on null\" — you never see Postmark's real error text.","triggerScenarios":"Any Postmark response with a status other than 200 enters the if-branch and detonates the bug: 401 {\"ErrorCode\":10,\"Message\":\"Unauthorized\"} from a wrong/expired postmark_key config; 422 {\"ErrorCode\":300,...} for an inactive/invalid recipient email or an unverified From sender domain; 500 from a Postmark incident. Each condition that should raise a readable exception instead crashes with an undefined-variable/undefined-class Error at this exact line.","commonSituations":"Developer copies the throw line from the Discord notifier (where the variable is $msg_response) into Postmark and never exercises the failure path in tests, so the typo ships; Postmark server token rotated or the sender domain's DKIM/return-path not yet verified, so the first real delivery failure exposes the fatal instead of the message.","solutions":["Fix the variable: replace $msg_response with $res in the throw statement.","Add \"use Exception;\" to the imports in src/Services/Mail/Postmark.php, otherwise PHP looks for App\\Services\\Mail\\Exception and errors with \"Class not found\".","Once the throw works, act on Postmark's body: ErrorCode 10 (401) means re-copy the Server Token from Postmark -> Settings -> API tokens; ErrorCode 300/other 422s mean the recipient is on the recipient suppression list (inactive/bounced) or From is not a verified sender.","Verify the postmark_sender address belongs to a domain with a confirmed DKIM/SPF signature in Postmark, and that postmark_stream matches an existing message stream (default is 'outbound').","Add a test that mocks a 401 Postmark response to prove the failure path returns the API's message instead of a PHP Error."],"exampleFix":"// before (src/Services/Mail/Postmark.php)\nuse App\\Models\\Config;\nuse GuzzleHttp\\Client;\n// ...\nif ($res->getStatusCode() !== 200) {\n    throw new Exception($msg_response->getBody()->getContents());\n}\n\n// after\nuse App\\Models\\Config;\nuse Exception;\nuse GuzzleHttp\\Client;\n// ...\nif ($res->getStatusCode() !== 200) {\n    $err = json_decode((string) $res->getBody(), true) ?: [];\n    throw new Exception(sprintf('Postmark %d (ErrorCode %s): %s',\n        $res->getStatusCode(), $err['ErrorCode'] ?? '?', $err['Message'] ?? 'unknown'));\n}","handlingStrategy":"try-catch","validationCode":"// Validate mail inputs before calling Postmark::send()\nif (!filter_var($to, FILTER_VALIDATE_EMAIL)) {\n    throw new InvalidArgumentException(\"Invalid recipient address: {$to}\");\n}\nif (!filter_var($configs['postmark_sender'] ?? '', FILTER_VALIDATE_EMAIL)) {\n    throw new InvalidArgumentException('postmark_sender config is not a valid email');\n}\nif (($configs['postmark_key'] ?? '') === '') {\n    throw new InvalidArgumentException('postmark_key config is empty');\n}","typeGuard":null,"tryCatchPattern":"// Until the $msg_response -> $res bug and missing \"use Exception;\" are fixed,\n// the failure path throws \\Error (call on null / class not found), not Exception:\ntry {\n    (new App\\Services\\Mail\\Postmark())->send($to, $subject, $body);\n} catch (\\Error $e) {\n    // PHP fatal from the broken throw line — \"Call to a member function getBody() on null\"\n    $log->error('Postmark send crashed (known bug): ' . $e->getMessage());\n} catch (\\Throwable $e) {\n    $log->error('Mail send failed: ' . $e->getMessage());\n}","preventionTips":["Patch the source first: use $res (not $msg_response) in the throw and add \"use Exception;\" — no caller-side guard fixes a fatal Error cleanly.","Verify the sender domain in Postmark (DKIM/return-path confirmed) and use a verified postmark_sender address.","Validate recipient addresses with filter_var(FILTER_VALIDATE_EMAIL) before sending to avoid predictable 422s.","Watch Postmark's ErrorCode in response bodies (10 = unauthorized, 300s = recipient suppressed) and disable the mail driver with an admin alert on 401 instead of retrying.","Add a failure-path unit test with a mocked 401 response so a broken throw line can never ship unnoticed again."],"tags":["php","postmark","email","guzzle","bug","undefined-variable","api-error"],"backgroundTag":"postmark-api-error","analyzedSha":"d55a607191cfc51cdbc836fba85196ddef4df343","analyzedAt":"2026-08-21T04:59:05.849Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}