{"record":{"id":"5aff131dba708185","repo":"henrygd/beszel","slug":"error-parsing-url-v","errorCode":null,"errorMessage":"error parsing URL: %v","messagePattern":"error parsing URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/alerts/alerts.go","lineNumber":256,"sourceCode":"\t\tFrom: mail.Address{\n\t\t\tAddress: am.hub.Settings().Meta.SenderAddress,\n\t\t\tName:    am.hub.Settings().Meta.SenderName,\n\t\t},\n\t}\n\terr = am.hub.NewMailClient().Send(&message)\n\tif err != nil {\n\t\treturn err\n\t}\n\tam.hub.Logger().Info(\"Sent email alert\", \"to\", message.To, \"subj\", message.Subject)\n\treturn nil\n}\n\n// SendShoutrrrAlert sends an alert via a Shoutrrr URL\nfunc (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link, linkText string) error {\n\t// Parse the URL\n\tparsedURL, err := url.Parse(notificationUrl)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing URL: %v\", err)\n\t}\n\tscheme := parsedURL.Scheme\n\tqueryParams := parsedURL.Query()\n\n\t// Add title\n\tif _, ok := supportsTitle[scheme]; ok {\n\t\tqueryParams.Add(\"title\", title)\n\t} else if scheme == \"mattermost\" {\n\t\t// use markdown title for mattermost\n\t\tmessage = \"##### \" + title + \"\\n\\n\" + message\n\t} else if scheme == \"generic\" && queryParams.Has(\"template\") {\n\t\t// add title as property if using generic with template json\n\t\ttitleKey := queryParams.Get(\"titlekey\")\n\t\tif titleKey == \"\" {\n\t\t\ttitleKey = \"title\"\n\t\t}\n\t\tqueryParams.Add(\"$\"+titleKey, title)\n\t} else {","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/internal/alerts/alerts.go#L238-L274","documentation":"SendShoutrrrAlert sends alerts via a Shoutrrr service URL (e.g. discord://token@id, smtp://...). The URL is parsed with url.Parse first; on failure the raw error is wrapped as `error parsing URL: %v`, meaning the notification URL string is malformed.","triggerScenarios":"url.Parse(notificationUrl) errors: invalid characters (unescaped spaces, control chars, a stray `%` like `100%`), or misplaced reserved characters such as an extra `@` or unescaped password, e.g. `discord://my@token@123456`.","commonSituations":"Users paste service URLs into config/env with unescaped special characters in tokens/passwords; template placeholders left unrendered; smart quotes or trailing whitespace from copy/paste.","solutions":["Percent-encode reserved characters in the URL's userinfo portion: `%` → %25, `@` → %40, spaces → %20.","Log/inspect the exact notificationUrl value; strip surrounding whitespace, quotes, and invisible characters.","Use shoutrrr's URL generator (`shoutrrr generate <service>`) instead of hand-assembling the URL.","If a password contains reserved characters, escape it or move credentials to a mechanism that doesn't require URL encoding."],"exampleFix":"// before\nnotificationUrl := \"discord://my@token@123456\"\n// after\nnotificationUrl := \"discord://my%40token@123456\" // @ in token percent-encoded","handlingStrategy":"validation","validationCode":"// validate the notification URL before sending\nif _, err := url.Parse(notificationUrl); err != nil {\n\treturn fmt.Errorf(\"invalid shoutrrr URL %q: %w\", notificationUrl, err)\n}","typeGuard":"func validShoutrrrURL(u string) bool {\n\tparsed, err := url.Parse(u)\n\treturn err == nil && parsed.Scheme != \"\"\n}","tryCatchPattern":"if err := am.SendShoutrrrAlert(notificationUrl, title, msg, link, linkText); err != nil {\n\tif strings.HasPrefix(err.Error(), \"error parsing URL\") {\n\t\tlog.Printf(\"check notification URL encoding for %%, @, spaces: %v\", err)\n\t} else {\n\t\treturn err\n\t}\n}","preventionTips":["Percent-encode tokens/passwords (%25, %40, %20).","Generate URLs with `shoutrrr generate` instead of by hand.","Trim whitespace/quotes from config values before use.","Test the URL once at startup with SendTestNotification."],"tags":["go","url-parsing","alerts","shoutrrr"],"backgroundTag":"malformed-url","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}